使用 Unity 掌握 UI 开发
Mastering UI Development with Unity
使用 Unity 开发引人入胜且身临其境的用户界面
Develop engaging and immersive user interfaces with Unity
Ashley Godbold 博士
Dr. Ashley Godbold
版权所有 © 2024 Packt Publishing
Copyright © 2024 Packt Publishing
保留所有权利。未经出版商事先书面许可,不得以任何形式或任何手段复制、存储于检索系统或传播本书的任何部分,但批判性文章或评论中嵌入的简短引文除外。
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
在编写本书的过程中,我们尽了最大努力确保所提供信息的准确性。但是,本书中包含的信息在出售时不提供任何明示或暗示的保证。作者、Packt Publishing 或其经销商和分销商均不对本书直接或间接造成的任何损害承担责任。
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing 已尽力以适当的大写字母形式提供本书中提及的所有公司和产品的商标信息。但是,Packt Publishing 无法保证这些信息的准确性。
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
集团产品经理:Rohit Rajkumar
Group Product Manager: Rohit Rajkumar
出版产品经理:Kaustubh Manglurkar
Publishing Product Manager: Kaustubh Manglurkar
图书项目经理:Sonam Pandey
Book Project Manager: Sonam Pandey
高级编辑:Debolina Acharyya
Senior Editor: Debolina Acharyya
技术编辑:Reenish Kulshrestha
Technical Editor: Reenish Kulshrestha
文字编辑:Safis 编辑
Copy Editor: Safis Editing
校对:Safis 编辑
Proofreader: Safis Editing
索引编制者:Rekha Nair
Indexer: Rekha Nair
制作设计师:维杰·坎布尔
Production Designer: Vijay Kamble
DevRel 营销协调员:Anamika Singh 和Nivedita Pandey
DevRel Marketing Coordinators: Anamika Singh and Nivedita Pandey
首次发布时间:2018 年 4 月
First published: April 2018
第二版:2024 年 6 月
Second edition: June 2024
生产编号:1080524
Production reference: 1080524
由 Packt Publishing Ltd.出版。
Published by Packt Publishing Ltd.
格罗夫纳大厦
Grosvenor House
11 圣保罗广场
11 St Paul’s Square
伯明翰
Birmingham
英国B3 1RB
B3 1RB, UK
ISBN 978-1-80323-539-4
ISBN 978-1-80323-539-4
Ashley Godbold 博士是一名程序员、艺术家、作家和教授。她拥有数学理学学士学位、数学理学硕士学位、游戏艺术与设计理学学士学位以及新兴媒体计算机科学博士学位,她的论文研究重点是教育视频游戏设计。她是一家 AA 游戏工作室的工程师,并经营着自己的小型独立工作室。她在大学教授游戏编程和开发、信息技术、计算机科学、数学和数据科学课程。她把闲暇时间用来和丈夫、女儿、狗和三只猫一起看动漫和玩电子游戏。
Dr. Ashley Godbold is a programmer, artist, author, and professor. She holds a Bachelor of Science in mathematics, a master of science in mathematics, a bachelor of science in game art and design, and a doctorate of computer science in emerging media, where her dissertation research focused on educational video game design. She is an engineer for an AA game studio and runs her own small indie studio. She teaches college courses in game programming and development, information technology, computer science, mathematics, and data science. She spends her free time watching anime and playing video games with her husband, daughter, dog, and three cats.
Dmitrii Ivashchenko是一位经验丰富的 Unity 游戏开发人员,在移动游戏和后端系统方面拥有超过 11 年的经验。在 MY.GAMES,他领导着一支致力于制作引人入胜的游戏的开发团队,为公司覆盖全球超过 10 亿用户的广泛产品组合做出了贡献。作为国际游戏开发者协会和互动艺术与科学学院的活跃成员,Dmitrii 通过文章和会议演讲分享他深厚的 Unity 知识。他还曾担任国际奖项的评委,并参与开源项目。他的目标是指导和提升游戏开发社区,体现他对创新的热情和对游戏创作卓越的追求。
Dmitrii Ivashchenko is a skilled Unity game developer with over 11 years of experience in mobile gaming and backend systems. At MY.GAMES, he leads a development team dedicated to crafting engaging games, contributing to the company’s extensive portfolio that reaches over 1 billion users globally. An active member of the International Game Developers Association and the Academy of Interactive Arts and Sciences, Dmitrii shares his profound Unity knowledge through articles and conference presentations. He has also served as a judge for international awards and participated in open source projects. He aims to mentor and elevate the game development community, reflecting his passion for innovation and the pursuit of excellence in game creation.
Michael Ross是一位自学成才的 UI/UX 远见者,他通过自己的公司 Neon Raven 将自己的专业知识应用于医疗、汽车、虚拟现实和游戏等行业。Michael 以开创性的 3D CT 成像和汽车工具而闻名,他旨在利用自己丰富的经验广泛分享自己的知识并支持 Packt Publishing 的使命。Neon Raven 是游戏开发和 UI 创新的融合,在 Michael 的领导下蓬勃发展,而 Nuno Duarte 的杰出艺术才华又让 Neon Raven 焕发生机,Nuno Duarte 无限的创造力和对完美的执着为 Neon Raven 的成功留下了不可磨灭的印记。Michael 和 Nuno 共同驾驭不断发展的 UI 开发领域,对创新的热情和对卓越的承诺将他们团结在一起。
Michael Ross, a self-taught UI/UX visionary, has applied his expertise across industries that include medical, automotive, VR, and gaming through his company, Neon Raven. Known for pioneering 3D CT imaging and automotive tools, Michael aims to use his diverse experience to share his knowledge broadly and support Packt Publishing’s mission. Neon Raven, where game development and UI innovation converge, thrives under Michael’s leadership, which is brought to life by the exceptional artistry of Nuno Duarte, whose boundless creativity and dedication to perfection has left an indelible mark on Neon Raven’s success. Together, Michael and Nuno navigate the ever-evolving realm of UI development, united by a passion for innovation and a commitment to excellence.
Unity 内置的游戏中可以加入大量内置 UI 元素。本书将深入介绍各种 UI 对象、功能和属性,并提供逐步实现的示例,帮助您掌握 Unity 的 UI 系统。
There are a multitude of built-in UI elements that can be incorporated into a game built in Unity. This book will help you master Unity’s UI system by describing, in-depth, the various UI objects, functionalities, and properties and providing step-by-step examples of their implementation.
本书面向使用过 Unity 并希望提高对 Unity 中提供的 UI 系统了解的游戏开发者。对于希望深入了解特定 UI 元素以及希望逐步了解如何实现多种游戏类型中出现的 UI 项目的个人,本书也会有所帮助。需要对 Unity 和 C# 编程有基本的了解。
This book is intended for game developers who have worked with Unity and are looking to improve their knowledge of the UI systems provided within Unity. Individuals looking for in-depth explanations of specific UI elements, as well as individuals looking for step-by-step directions explaining how to implement UI items that appear in multiple game genres, will also find this book helpful. A basic understanding of Unity and C# programming is needed.
第 1 章“设计用户界面”涵盖了与设计用户界面相关的基本信息。它定义了图形用户界面和用户界面之间的区别。它讨论了四种不同类型的游戏界面、如何根据设计原则创建美观的 UI 以及界面隐喻的概念。此外,还详细解释了如何设置 Unity 项目的纵横比和分辨率。
Chapter 1, Designing User Interfaces, covers basic information related to designing user interfaces. The distinction between a graphic user interface and user interface is defined. It discusses the four different types of game interfaces, how to create an aesthetically pleasing UI based on principles of design, and the concept of interface metaphors. Additionally, a detailed explanation of setting the aspect ratio and resolution of a Unity project is discussed.
第 2 章“设计移动用户界面”涵盖了 UI 设计师在开发移动应用时必须考虑的美学和机械方面的因素。此外,它还讨论了可供开发人员使用的资源,以帮助他们设计移动用户界面。
Chapter 2, Designing Mobile User Interfaces, covers considerations that a UI designer must take into account when developing for mobile, both aesthetically and mechanically. Additionally, it discusses the resources available to developers to help them design mobile user interfaces.
第 3 章“设计 VR、MR 和 AR UI”涵盖了为 VR、MR 和 AR 应用程序设计用户界面的基本概念。它研究了这些应用程序中的交互与其他应用程序的不同之处,并讨论了设计它们的最佳实践。
Chapter 3, Designing VR, MR, and AR UI, covers the basic concepts of designing user interfaces for VR, MR, and AR applications. It looks at how interactions in these applications differ from other applications and discusses the best practices for designing them.
第 4 章,UI 的通用设计和可访问性,涵盖了与设计用户界面相关的基本概念,以便让尽可能多的人使用它们。本章将探讨通用设计和可访问性设计的主题,同时讨论 UI 设计师可以采取哪些步骤来确保他们的用户界面尽可能无障碍。
Chapter 4, Universal Design and Accessibility for UI, covers basic concepts related to designing user interfaces so that they can be used by the greatest number of people. This chapter will explore the topic of universal design and designing for accessibility while discussing steps that a UI designer can take to make sure their user interfaces are as barrier-free as possible.
第 5 章Unity中的用户界面和输入系统回顾了 Unity 提供的各种 UI 系统。Unity 提供了三个用于设计用户界面的系统和两个用于控制输入的系统。本章探讨了各种系统,比较了它们的优点,并讨论了何时使用其中哪一个。
Chapter 5, User Interface and Input Systems in Unity, reviews the various systems provided by Unity to work with UI. Unity provides three systems for designing user interfaces and two systems for controlling the inputs. This chapter explores the various systems, compares their benefits, and discusses when to use which one of them.
第 6 章“画布、面板和基本布局”探讨了如何在画布中适当布局 UI 元素来开发用户界面。本章将使用面板,并介绍文本和图像。本章中包含的示例将向您展示如何布局基本平视显示器、创建永久背景图像以及开发基本弹出菜单。
Chapter 6, Canvases, Panels, and Basic Layouts, explores the development of a user interface by appropriately laying out UI elements within a Canvas. Panels are used, and an introduction to Text and Images is also provided. The examples included in this chapter show you how to lay out a basic heads up display, create a permanent background image, and develop a basic pop-up menu.
第 7 章探索自动布局讨论了如何实现各种自动布局组件以简化 UI 构建过程。本章中包含的示例利用自动布局功能在 HUD 中创建选择菜单和网格库存。
Chapter 7, Exploring Automatic Layouts, discusses how to implement the various automatic layout components to streamline the UI building process. The examples included within this chapter utilize the automatic layout functionality to create a selection menu in the HUD and a gridded inventory.
第 8 章,事件系统和 UI 编程,介绍了事件系统及其与 UI 的关系。讨论了如何向 UI 元素添加事件触发器。它介绍了为 UI 系统编程所需的关键字、如何通过代码访问 UI 组件以及如何编写可通过事件触发器访问的函数。
Chapter 8, The Event System and Programming for UI, covers the event system and how it pertains to the UI. How to add Event Triggers to UI elements is discussed. It covers the keywords necessary to program for the UI system, how to access UI components via code, and how to write functions that can be accessed via Event Triggers.
第 9 章UI按钮组件探讨了按钮的各种属性。本章中的示例介绍了如何设置按钮的键盘和控制器导航、如何在按下按钮时加载场景、如何创建动画按钮过渡以及如何使按钮交换其图像。
Chapter 9, The UI Button Component, explores the various properties of buttons. The examples in this chapter walk through how to set up keyboard and controller navigation of buttons, how to load scenes when buttons are pressed, how to create animated button transitions, and how to make buttons swap their images.
第 10 章UI文本和 Text-TextMeshPro更详细地讨论了文本的属性,并演示了如何通过代码影响其属性。本章末尾的示例展示了如何创建一个对话框,其中的文本会像输入一样动画,如何创建自定义字体,以及如何创建使用渐变换行的文本。
Chapter 10, UI Text and Text-TextMeshPro, discusses the properties of text more thoroughly and demonstrates how to affect their properties via code. The examples at the end of the chapter show how to create a dialog box with text that animates as if it were being typed in, how to create a custom font, and how to create text that wraps with a gradient.
第 11 章UI图像和效果展示了使用和操作 UI 图像的更多方法。此外,它还演示了如何将各种效果应用于UI 元素。
Chapter 11, UI Images and Effects, shows more ways in which UI images can be used and manipulated. Additionally, it demonstrates how to apply various effects to UI elements.
第 12 章“使用蒙版、滚动条和滚动视图”介绍如何创建带有蒙版的可滚动窗口,以便您的 UI 可以容纳比立即可见的更多的项目。
Chapter 12, Using Masks, Scrollbars, and Scroll Views, covers how to create scrollable windows with masks so that your UI can hold more items than are immediately in view.
第 13 章“其他可交互 UI 组件本章末尾介绍了如何使用各种输入以及如何创建带图像的下拉菜单的示例
Chapter 13, Other Interactable UI Components, covers a myriad of other UI inputs. Examples of how to use the various inputs and how to create a dropdown menu with images are covered at the end of the chapter.
第 14 章“动画 UI 元素”主要介绍动画 UI。本章中的示例展示了如何为菜单添加动画效果以淡入淡出,以及如何使用 Unity状态机创建宝箱动画。
Chapter 14, Animating UI Elements, is all about animating the UI. The examples in this chapter show how to animate menus to fade in and out and how to create a treasure box animation using the Unity State Machine.
第 15 章“UI 中的粒子”扩展了上一章的动画示例,并提供了使用粒子效果美化 UI 的更多方法。
Chapter 15, Particles in the UI, expands upon the animation example of the previous chapter and provides further ways in which you can zhuzh up your UI using particle effects.
第 16 章“利用世界空间 UI”展示了如何创建存在于游戏场景中的 UI 元素,而不是存在于所有游戏内物品前面的“屏幕”中。示例涵盖了如何为 2D 场景创建交互式 UI 以及为3D 场景创建交互式悬停健康栏。
Chapter 16, Utilizing World Space UI, showcases how to create UI elements that exist within the game scene as opposed to on the “screen” in front of all in-game items. The examples cover how to create an interactive UI for a 2D scene and interactive, hover health bars for a 3D scene.
第 17 章优化Unity UI介绍了创建优化用户界面的基本概念。它定义了关键术语,概述了 Unity 中包含的工具(可帮助您确定游戏的性能),并介绍了使用 Unity UI 系统的各种优化策略。
Chapter 17, Optimizing Unity UI, covers basic concepts of creating optimized user interfaces. It defines key terms, provides an overview of tools included within Unity that can help you determine how performant your game is, and covers various optimization strategies for working with Unity’s UI system.
第 18 章,UI Toolkit 入门,介绍了新的 Unity UI Toolkit,并解释了如何使用它来创建基本布局。它介绍了使用这个不同 UI 系统的关键概念,同时还演示了如何使用它来创建编辑器和运行时 UI。
Chapter 18, Getting Started with UI Toolkit, covers the new Unity UI Toolkit and explains how to use it to create basic layouts. It covers the key concepts of using this different UI system, while also demonstrating how to use it to create both Editor and runtime UI.
第 19 章使用IMGUI讨论了如何使用 IMGUI 系统构建用户界面。在探索 IMGUI 的基本概念之后,本章介绍了如何使用该系统创建在编辑器中和运行时出现的开发人员工具。
Chapter 19, Working with IMGUI, discusses how to use the IMGUI system to build user interfaces. After exploring the basic concepts of IMGUI, the chapter covers how to use the system to create developer tools that appear in the Editor and at runtime.
第 20 章“新输入系统”介绍了如何使用新输入系统轻松进行输入设置。它涵盖了发布者-订阅者架构模式,同时介绍了输入系统的基本概念和原理。此外,它还介绍了如何将使用输入管理器的项目更新为使用新输入系统的项目,以及如何通过两种不同的方式将代码连接到输入系统。
Chapter 20, The New Input System, provides an introduction to using the new Input System for easy input setup. It covers the publisher-subscriber architectural pattern while introducing the basic concepts and principles of the Input System. Additionally, it covers how to update a project that uses the Input Manager to one that uses the new Input System, as well as how to connect your code to the Input System in two different ways.
本书假设您对 Unity 编辑器的导航和使用有很好的理解。此外,本书还假设您对使用 C# 进行 Unity 编程有基本的了解。
This book assumes you have a good understanding of navigating within and working with the Unity Editor. Additionally, it assumes you have a basic understanding of programming in C# for Unity.
|
本书涵盖的软件/硬件 Software/hardware covered in the book |
操作系统要求 Operating system requirements |
|
Unity 2020或更高版本 Unity 2020 or higher |
Windows、macOS或 Linux Windows, macOS, or Linux |
除了安装 Unity 之外,您还需要一个代码编辑器 (IDE)。虽然本书没有指定首选 IDE,但支持的 IDE 示例包括 Visual Studio 和JetBrains Rider。
In addition to having Unity installed, you will also need a code editor (IDE). While no preference is established in this book, examples of supported IDEs are Visual Studio and JetBrains Rider.
如果您使用的是本书的数字版,我们建议您自己输入代码或从本书的 GitHub 存储库访问代码(下一节将提供链接)。这样做将帮助您避免与复制和粘贴代码相关的任何潜在错误。
If you are using the digital version of this book, we advise you to type the code yourself or access the code from the book’s GitHub repository (a link is available in the next section). Doing so will help you avoid any potential errors related to the copying and pasting of code.
您可以从 GitHub 下载本书的示例代码文件,网址为https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition。如果代码有更新,它将在GitHub 存储库中更新。
You can download the example code files for this book from GitHub at https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition. If there’s an update to the code, it will be updated in the GitHub repository.
我们丰富的书籍和视频目录中还有其他代码包,可在https://github.com/PacktPublishing/上找到。快去看看吧!
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
本书使用了许多文本约定。
There are a number of text conventions used throughout this book.
文本中的代码:表示文本中的代码字、数据库表名称、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。以下是示例:“目前, AnimationComplete触发器有点问题。”
Code in text: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: “Currently, there is a bit of a problem with the AnimationComplete trigger.”
代码块设置如下:
A block of code is set as follows:
[System.Serializable]public class Translation { public string languageKey; public string TranslationString; public Font font; public FontStyle fontStyle;}[System.Serializable]public class Translation { public string languageKey; public string translatedString; public Font font; public FontStyle fontStyle;} 当我们想让你注意代码块的某个特定部分时,相关的行或项目会以粗体显示:
When we wish to draw your attention to a particular part of a code block, the relevant lines or items are set in bold:
Vector2[] newPositions = new Vector2[]{ Input.GetTouch(0).position, Input.GetTouch(1).position};Vector2[] newPositions = new Vector2[]{Input.GetTouch(0).position, Input.GetTouch(1).position}; 粗体:表示新术语、重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。以下是示例:“当为可视性属性选择永久时,如果允许相应的移动,则相应的滚动条将保持可见,即使不需要它。”
Bold: Indicates a new term, an important word, or words that you see on screen. For instance, words in menus or dialog boxes appear in bold. Here is an example: “When Permanent is selected for the Visibility property, the respective scrollbar will remain visible, even if it is not needed, if its corresponding movement is allowed.”
提示或重要说明
Tips or important notes
呈现如下状态。
Appear like this.
我们随时欢迎读者的反馈。
Feedback from our readers is always welcome.
一般反馈:如果您对本书的任何方面有疑问,请发送电子邮件至customercare@packtpub.com ,并在邮件主题中注明书名。
General feedback: If you have questions about any aspect of this book, email us at customercare@packtpub.com and mention the book title in the subject of your message.
勘误表:尽管我们已尽一切努力确保内容的准确性,但错误还是难免。如果您发现本书中有错误,请向我们报告,我们将不胜感激。请访问www.packtpub.com/support/errata并填写表格。
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata and fill in the form.
盗版:如果您在互联网上发现任何形式的我们作品的非法复制品,我们将非常感激您向我们提供位置地址或网站名称。请通过copyright@packt.com与我们联系,并提供材料链接。
Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at copyright@packt.com with a link to the material.
如果您有兴趣成为一名作家:如果您对某个主题有专业知识,并且有兴趣撰写或参与撰写书籍,请访问authors.packtpub.com。
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
阅读完《使用 Unity 掌握 UI 开发》后,我们很想听听您的想法!请单击此处直接进入本书的 Amazon 评论页面并分享您的反馈。
Once you’ve read Mastering UI Development with Unity, we’d love to hear your thoughts! Please click here to go straight to the Amazon review page for this book and share your feedback.
您的评论对我们和技术社区都很重要,并将帮助我们确保提供优质的内容。
Your review is important to us and the tech community and will help us make sure we’re delivering excellent quality content.
感谢您购买本书!
Thanks for purchasing this book!
您是否喜欢在旅途中阅读但又无法随身携带纸质书籍?
Do you like to read on the go but are unable to carry your print books everywhere?
您购买的电子书是否与您选择的设备不兼容?
Is your e-book purchase not compatible with the device of your choice?
别担心!现在购买每本 Packt 书籍,您都可以免费获得该书的无 DRM 的 PDF 版本。
Don’t worry!, Now with every Packt book, you get a DRM-free PDF version of that book at no cost.
随时随地、使用任何设备进行阅读。搜索、复制您喜爱的技术书籍中的代码,并将其直接粘贴到您的应用程序中。
Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.
福利不止于此,您还可以每天在收件箱中独家获得折扣、新闻通讯和精彩的免费内容
The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily
按照以下简单步骤即可获得好处:
Follow these simple steps to get the benefits:
https://packt.link/free-ebook/9781803235394
https://packt.link/free-ebook/9781803235394
在本部分中,您将大致了解在为各种平台设计用户界面时需要考虑的设计原则。本部分探讨了如何为手机游戏和 XR 体验的特殊情况设计 UI。此外,本部分还介绍了如何在考虑通用设计和可访问性的情况下为所有平台设计 UI。最后,本部分比较并讨论了 Unity 中促进 UI 开发的各种系统。
In this part, you will get a general overview of design principles to consider while designing user interfaces for various platforms. How to design UI for the special cases of mobile games and XR experiences is explored. Additionally, how to design UI for all platforms with consideration for universal design and accessibility is covered. Lastly, the various systems within Unity that facilitate the development of UI are compared and discussed.
本部分包含以下章节:
This part has the following chapters:
在使用 UI 时,了解一些设计基础知识非常重要。本章将介绍 UI 设计的基础知识和一些关键概念,帮助您朝着正确的方向前进。
When working with UI, it is important to understand a few design basics. This chapter will cover the foundation of designing UI and a few key concepts to start you off in the right direction.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
本书不是关于 UI 设计艺术的。它是一本讨论 UI 功能实现的技术文本。但是,我确实想讨论一些 UI 设计的基本设计原则。我不指望你读完本章后就能成为一名出色的 UI 设计师。不过,我确实希望你从本章中获得一些关于布局和设计原则的基本了解,这样你的艺术家朋友就不会太取笑你了。
This book is not about the art of designing UI. It is a technical text that discusses the implementation of UI functionality. However, I do want to discuss some basic design principles of UI design. I don’t expect you to be an amazing UI designer after reading this chapter. I do hope that you get some basic understanding of layout and design principles from this chapter, though, so that maybe your artist friends won’t make too much fun of you.
对于本章,您将需要以下内容:
For this chapter, you will need the following:
Unity 2020.3.26f1或更高版本
Unity 2020.3.26f1 or later
那么,到底UI 和 GUI 分别代表什么?区别?UI代表用户界面,GUI(发音为“gooey”)代表图形用户界面。界面意味着与...交互,因此 UI 是让玩家与游戏交互的一组设备。鼠标、键盘、游戏控制器、触摸屏等都是 UI 的一部分。GUI 是图形表示的 UI 的子集。因此,屏幕按钮、下拉菜单和图标都是游戏 GUI 的一部分。由于 GUI 是 UI 的一个子集,许多人(包括我自己)倾向于将 GUI 称为 UI。Unity 还将他们为其提供模板的所有 GUI 项目称为UI。
So, what exactly do UI and GUI stand for, and what’s the difference? UI stands for user interface and GUI (pronounced “gooey”) stands for graphical user interface. To interface means to interact with, so the UI is the set of devices that let a player interact with a game. The mouse, keyboard, game controller, touch screen, and so on are all part of the UI. The GUI is the subset of the UI represented by graphics. So, onscreen buttons, dropdown menus, and icons are all part of a game’s GUI. As the GUI is a subset of the UI, many people (myself included) tend to just refer to the GUI as the UI. Unity also refers to all the GUI items they provide templates for as the UI.
本书将主要关注 GUI 设计,但它将讨论UI 控件的一些非图形方面,例如通过鼠标、屏幕点击、键盘或控制器访问数据。本章将特别介绍不同界面类型的一些基本设计注意事项。
This book will focus primarily on GUI design, but it will discuss some non-graphical aspects of UI controls, such as accessing data from the mouse, screen tap, keyboard, or controller. This chapter specifically will look at some basic design considerations for different interface types.
当你说“游戏 UI”时,大多数人们想到了出现在前方的平视显示器(HUD )所有游戏内物品。然而,游戏界面实际上有四种不同的类型:非叙事界面、叙事界面、元界面和空间界面。
When you say “game UI,” most people think of the heads-up display (HUD) that appears in front of all the in-game items. However, there are actually four different types of game interfaces: non-diegetic, diegetic, meta, and spatial.
Fagerholt 和 Lorentzon 在 2009 年的论文《超越 HUD:提高 FPS 游戏中玩家沉浸感的用户界面:理学硕士论文》中首次描述了这四种不同的界面类型。从那时起,该术语就被广泛应用于整个 UI 游戏设计领域。您可以在http://publications.lib.chalmers.se/records/fulltext/111921.pdf找到原始出版物。
Fagerholt and Lorentzon first described these four different interface types in the 2009 paper Beyond the HUD: User Interfaces for Increased Player Immersion in FPS Games: Master of Science Thesis. Since then, the terminology has been widely used throughout the field of UI game design. You can find the original publication at http://publications.lib.chalmers.se/records/fulltext/111921.pdf.
四者之间的区别是通过以下两个维度的交叉来确定的:
The distinction between the four is determined by a cross of the following two dimensions:
下图演示了两个问题之间的交叉关系以及它们如何定义四种类型的接口:
The following diagram demonstrates the cross relationship between the two questions and how they define the four types of interfaces:
图 1.1:四种类型的接口
Figure 1.1: Four types of interfaces
游戏的HUD掉落归入非叙事类别。这些信息仅供玩家查看,游戏中的角色并不知道它的存在。它存在于游戏视图的第四面墙上,似乎出现在屏幕上,位于一切事物的前面。示例这种类型的 UI 是无穷无尽的,因为几乎每个游戏都有一些非叙事性的UI 元素。
A game’s HUD falls into the non-diegetic category. This information exists purely for the player to view and the characters within the game are not aware of its presence. It exists on the fourth wall of the game view and appears to be on the screen in front of everything. The examples of this type of UI are endless, as nearly every game has some non-diegetic UI elements.
或者说,叙事界面存在于游戏世界中,游戏内的角色也知道它的存在。常见的例子包括角色查看库存或地图。最广为人知的叙事 UI 示例是《死亡空间》中的库存和生命值显示。库存显示在可玩角色面前弹出的全息显示窗口中,当您选择他的武器时,他可以与窗口交互。他背后的计量表也显示他的生命值。《鬼屋魔影》(2008)的库存也以叙事方式显示。虽然有些 UI 元素只有玩家可以看到,但主角会在夹克口袋中查看库存并与物品交互。《神秘海域:失落的遗产》和《孤岛惊魂 2》都使用角色在场景中实际持有并与之交互的地图。《辐射 3》和《辐射 4》使用叙事界面在角色的 Pip-Boy 上显示库存和地图,Pip-Boy 永久附在角色的手臂上。当角色乘坐车辆或套装时,游戏中也会使用这种类型的显示,其中盾牌、窗户或驾驶舱上会出现各种显示。
Alternatively, a diegetic interface is one that exists within the game world and the characters within the game are aware of its presence. Common examples of this include characters looking at inventory or maps. The most widely referred-to example of diegetic UI is the inventory and health display within Deadspace. The inventory displays on a holographic display window that pops up in front of the playable character, and he interacts with it as you select his weaponry. His health is also indicated by a meter on his back. The inventory of Alone in the Dark (2008) is displayed in a diegetic way as well. While there are some UI elements that only the player can see, the main character views inventory within their jacket pockets and interacts with the items. Uncharted Lost Legacy and Far Cry 2 both use maps that the characters physically hold in the scene and interact with. Fallout 3 and Fallout 4 use a diegetic interface to display the inventory and map on the character’s Pip-Boy, which is permanently attached to their arm. Games also use this type of display when characters are in a vehicle or suit, where various displays appear on the shield, window, or cockpit.
元接口游戏中的角色可以感知到的界面,但它们并未在场景中实际显示。常见的例子是赛车游戏的速度显示。《极限竞速 7》实际上结合使用了元显示和叙事显示来显示速度表。元速度指示器始终显示在屏幕的右下角以供玩家查看。由于角色始终知道自己的驾驶速度,所以他们会注意到这个速度指示器,因此它就是一个元界面。汽车仪表板上还有一个叙事速度表,在第一人称视角下玩游戏时会显示出来。这种显示类型的另一种常见用途是出现在屏幕上但暗示可玩角色正在与之交互的手机。《女神异闻录 5》、《凯瑟琳》和《侠盗猎车手 5》都使用这种界面类型进行手机交互。
Meta interfaces are interfaces that the characters in the game are aware of, but they are not physically displayed within the scene. Common examples of this are speed displays for racing games. Forza 7 actually uses a combination of meta and diegetic displays for the speedometer. A meta speed indicator is persistently on the lower-right corner of the screen for the player to see. Since the character is constantly aware of how fast they are driving, they would be aware of this speed indicator, therefore making it a meta interface. There is also a diegetic speedometer in the car’s dash that is displayed when playing in first-person view. Another common usage of this type of display is a cell phone that appears on the screen but is implied the playable character is interacting with. Persona 5, Catherine, and Grand Theft Auto 5 all use this interface type for cell phone interactions.
最后一种类型的界面,空间,存在于场景中存在但游戏内的角色却不知道的界面。场景中存在但角色不知道的界面非常常见。这通常用于让玩家知道场景中可交互物品的位置、游戏内角色在做什么或有关场景中角色和物品的信息。例如,在《塞尔达传说:荒野之息》中,敌人头上会出现箭头,指示林克将攻击谁。林克实际上并不知道这些箭头图标;它们存在是为了让玩家知道他正在关注谁。异度之刃 2使用空间界面来指示玩家可以在哪里挖掘,方法是在可挖掘区域上方显示铲子图标。
The last type of interface, spatial, exists in the scene, but the characters within the game are not aware of it. Interfaces that exist in the scene but that the characters are not aware of are incredibly common. This is commonly used to let the player know where in the scene interactable items are, what the in-game character is doing, or information about characters and items in the scene. For example, in Legend of Zelda: Breath of the Wild, arrows appear over the heads of enemies, indicating who Link will attack. Link is not actually aware of these arrow icons; they are there for the player to know who he is focusing on. Xenoblade Chronicles 2 uses a spatial interface to indicate where the player can dig by displaying a shovel icon over the diggable areas.
在设计游戏的 UI 布局时,我强烈建议您查看同类型的其他游戏,并看看他们是如何实现 UI 的。玩游戏,看看你是否感觉良好。
When laying out the UI for your game, I strongly recommend checking other games of the same genre and seeing how they implemented their UI. Play the game and see whether it feels good to you.
如果你不确定如何布局游戏的 UI,我建议将游戏屏幕分成一个带凹槽的网格,如下图所示,并将项目放置在非凹槽区域内:
If you are unsure of how to lay out your game’s UI, I recommend dividing the game’s screen into a guttered grid, like the one shown in the following diagram, and placing items within the non-guttered areas:
图 1.2:带沟槽的网格
Figure 1.2: A guttered grid
您可以根据需要使用任意数量的网格,但参考网格布置项目将有助于确保 UI 以平衡的方式排列。
You can use as many grids as you want, but laying out the items with reference to the grid will help ensure that the UI is arranged in a balanced way.
在大多数情况下,HUD 项目应保留在网格的外边缘。任何显示在中心网格中的 UI 都会限制玩家的视野。因此,此区域适合用于暂停游戏的弹出窗口。
In most cases, the HUD items should remain at the outer edges of the grid. Any UI that displays in the center grids will restrict the player view. So, this area is good for pop-up windows that pause the gameplay.
该设备在确定布局时,游戏将在哪个设备上进行非常重要。如果你的游戏是为移动设备设计的,并且有很多按钮供玩家交互,那么这些按钮通常最适合放在屏幕的底部或侧面。这是因为玩家握持手机的方式,屏幕的顶部中央部分是他们用拇指最难触及的区域。此外,伸手触及这个区域会导致他们用手挡住大部分游戏视图。我们将在第 2 章中更详细地讨论为移动设备设计 UI 。
The device your game will be played on is important when determining the layout. If your game is designed for a mobile device and has a lot of buttons the player will interact with, the buttons are generally best suited for the bottom or side portions of the screen. This is due to the way players hold their phones and the top-center part of the screen is the most difficult area to reach with their thumb. Additionally, reaching for this area will cause them to block the majority of the game view with their hand. We will discuss designing UI for mobile more thoroughly in Chapter 2.
您会注意到,玩电脑游戏时,它们的 UI 往往比手机和主机游戏小得多且更杂乱。这是由于可视性和交互性。用鼠标点击小物体比用手指点击或用方向键选择要容易得多。此外,屏幕分辨率要大得多,这允许UI占用更多空间。
You’ll note that when you play computer games, they tend to have much smaller and more cluttered UI than mobile and console games. This is due to visibility and interaction. Clicking on small objects with a mouse is significantly easier than tapping them with a finger or selecting them with the D-pad. Also, the screen resolution is much bigger, which allows for more space to be taken up by the UI.
在尝试确定 UI 项目的大小和相对位置时,可以参考菲茨定律。菲茨定律可以根据 UI 项目的大小和与用户起始位置的距离,用数学方法计算出用户导航到 UI 项目需要多长时间。我不会在这里讨论数学问题(尽管我内心的数学老师非常想这样做),但可以从菲茨定律中得到以下教训:
When trying to determine the size and relative location of UI items, you can reference Fitts’ Law. Fitts’ Law can mathematically calculate how long it will take a user to navigate to a UI item based on its size and distance away from the user’s starting position. I won’t go over the math here (despite the math teacher in me desperately wanting to), but the lessons that can be garnered from Fitts’ Law are as follows:
接下来,我们来讨论分辨率和纵横比。
Next, we’ll look at resolution and aspect ratio.
游戏的分辨率是游戏屏幕的像素尺寸。例如,游戏可以在 1,024x768 分辨率下运行。这意味着游戏宽度为 1,024 像素和 768 像素高。游戏的宽高比是宽度和高度的比率(表示为宽度:高度)。此宽高比通过将分辨率宽度除以分辨率高度然后简化分数来确定。因此,例如,如果您的游戏的分辨率为 1024x768,则宽高比将如下所示:
A game’s resolution is the pixel dimension of the screen on which it plays. For example, a game could run at 1,024x768. This means that the game is 1,024 pixels wide and 768 pixels tall. The aspect ratio of a game is the ratio of the width and height (expressed as width:height). This aspect ratio is determined by dividing the resolution width by the resolution height and then simplifying the fraction. So, for example, if your game has a resolution of 1024x768, the aspect ratio would be as follows:
1024像素/768像素=4/3
1024px/768px=4/3
这里,分数 4/3 表示纵横比 4:3。
Here, the fraction 4/3 is the aspect ratio 4:3.
下表列出了常见的宽高比和相关分辨率:
The following table provides a list of common aspect ratios and related resolutions:
图 1.3:常见的宽高比和分辨率
Figure 1.3: Common aspect ratios and resolutions
在设计 UI 时,分辨率和宽高比对于 UI 的外观起着重要作用。了解目标设备的分辨率和宽高比是设计 UI 的重要第一步,原因有二:
When designing your UI, the resolution and aspect ratio will play an important role in how your UI will look. Knowing the resolution and aspect ratio of your target device will be an important first step in designing your UI for two reasons:
如果您构建单一分辨率/宽高比,则 UI 构建起来会容易得多,因为您不必确保所有元素在多个宽高比下保持其相对位置。但是,如果您构建的游戏将在多个分辨率/宽高比下运行(例如,移动项目或在窗口内缩放的 Web 游戏),您希望您的 UI适当缩放和移动。您还希望能够轻松在测试期间更改分辨率,以便确保 UI 在显示窗口变形时定位正确。
If you build to a single resolution/aspect ratio, the UI will be much easier to build as you won’t have to make sure all the elements maintain their relative position at multiple aspect ratios. However, if you build a game that will run at multiple resolutions/aspect ratios (for example, a mobile project or a web game that scales within a window), you want your UI to scale and move appropriately. You’ll also want to be able to easily change the resolution during testing so that you can make sure the UI is positioned appropriately as its display window morphs.
即使您允许分辨率和宽高比发生变化,您仍应确定默认分辨率。此默认分辨率代表理想设计的分辨率。这将是您的初始设计和 UI 布局所基于的分辨率,因此如果分辨率或宽高比发生变化,UI 将尽可能保持相同的设计。
Even if you will allow your resolution and aspect ratio to vary, you should still decide on a default resolution. This default resolution represents the resolution of your ideal design. This will be the resolution that your initial design and UI layout are based on, so if the resolution or aspect ratio varies, the UI will try to maintain the same design as best it can.
笔记
Note
由于目前销售的所有电视的宽高比均为 16:9,因此您为主机游戏制作的任何 UI 都应考虑 16:9 的宽高比进行开发。
Since all televisions sold today have a 16:9 aspect ratio, any UI you make for a console game should be developed with a 16:9 aspect ratio in mind.
你可以在“游戏”选项卡中轻松切换不同的分辨率和宽高比。这将允许您查看你的 UI 如何在不同的分辨率和宽高比下缩放:
You can easily switch between different resolutions and aspect ratios in the Game tab. This will allow you to see how your UI scales at the different resolutions and aspect ratios:
图 1.4:从游戏视图中选择自由比例模式
此列表中显示的项目是您当前选择的构建目标最常见的宽高比和分辨率。在上图的屏幕截图中,我的构建目标是PC、Mac 和 Linux Standalone,因此最常见的显示器设置。如果我将我的构建目标更改为 iOS,我将看到流行的 iPhone 和 iPad 屏幕尺寸列表。
自由比例意味着游戏的宽高比将根据游戏视图的窗口缩放。因此,通过在游戏窗口上移动框架,您将更改宽高比。
Figure 1.4: Selecting Free Aspect mode from the Game view
The items displayed in this list are the most common aspect ratios and resolutions for the build target you currently have selected. In the preceding screenshot, my build target was PC, Mac & Linux Standalone, so the most common monitor settings are displayed. If I were to change my build target to iOS, I would see a list of popular iPhone and iPad screen dimensions.
Free Aspect means that the game’s aspect ratio will scale relative to the window of the Game view. So, by moving the frame around on the Game window, you will change the aspect ratio.
图 1.5:更改编辑器布局
图 1.6:2×3 布局的结果
Figure 1.5: Changing the Editor Layout
Now the Game and Scene tabs will both be visible on the left-hand side of your screen.
Figure 1.6: Results of the 2 by 3 layout
图 1.7:在自由比例模式下调整游戏视图大小的结果
Figure 1.7: Results of resizing the Game view in Free Aspect mode
图 1.8:游戏视图比例
Figure 1.8: Game view scale
图 1.9:添加新的分辨率或宽高比预设
例如,如果你想要制作一款让人回想起旧的 Game Boy 游戏,您可以添加 160x144像素预设:
图 1.10:创建固定分辨率预设
Figure 1.9: Adding a new resolution or aspect ratio preset
For example, if you wanted to make a game that was reminiscent of an old Game Boy game, you could add a 160x144 pixels preset:
Figure 1.10: Creating a fixed resolution preset
图 1.11:选择自定义预设
Figure 1.11: Selecting a custom preset
如果你创建计划在PC、Mac 和 Linux 独立目标平台上构建的游戏时,您可以强制分辨率始终相同。为此,请转到编辑|项目设置|播放器。您的检查器现在应显示以下内容:
If you are creating a game that you plan to build on the PC, Mac, & Linux Standalone target platform, you can force the resolution to always be the same. To do so, go to Edit | Project Settings | Player. Your Inspector should now display the following:
图 1.12:PC、Mac 和 Linux 独立播放器分辨率设置
Figure 1.12: PC, Mac & Linux Standalone Player resolution settings
这里显示的平台可能会更多或更少;这取决于您使用 Unity 安装的模块。
You may have more or fewer platforms displayed here; it depends on the modules you have installed with Unity.
要在PC、Mac 和 Linux 独立游戏中强制使用特定分辨率,请取消选择默认为原始分辨率。系统将为您提供输入默认屏幕宽度和默认屏幕高度的选项,您可以输入所需的分辨率值。然后,当您构建游戏时,它将以您指定的尺寸播放。
To force a specific resolution on a PC, Mac, & Linux Standalone game, deselect Default is Native Resolution. The option to input Default Screen Width and Default Screen Height will be made available to you and you can enter the desired resolution values. Then, when you build your game, it will play at the size you specified.
以下屏幕截图显示了强制 PC 游戏在 Game Boy Color 尺寸的窗口中播放的设置:
The following screenshot shows the settings for forcing a PC game to play in a window with Game Boy Color dimensions:
图 1.13:设置特定的 PC、Mac 和 Linux 独立播放器分辨率
Figure 1.13: Setting a specific PC, Mac, & Linux Standalone Player resolution
您还可以使用WebGL构建强制使用特定分辨率。需要担心的选项较少,但总体概念是相同的。以下屏幕截图显示了在WebGL的播放器设置中强制游戏以160 x 140显示的设置:
You can also force a specific resolution with a WebGL build. There are fewer options to worry about, but the general concept is the same. The following screenshot shows the settings for forcing your game to display at 160x140 in the Player Settings for WebGL:
图 1.14:设置特定的 WebGL 分辨率
Figure 1.14: Setting a specific WebGL resolution
在第 2 章中,我们将讨论如何为具有不同分辨率且无法预先定义的手机游戏设置分辨率属性。
In Chapter 2, we will discuss how to set the resolution properties for mobile games that have varying resolutions that you cannot pre-define.
本章讨论了一些与 UI 相关的设计原则和术语。现在您应该能够区分 GUI 和 UI,并定义四种界面类型:叙事界面、空间界面、元界面和非叙事界面。此外,您还应该了解一些布局 UI 的基本规则以及如何在不同分辨率和宽高比下工作。
This chapter discussed some basic design principles and terminology related to UI. You should now be able to distinguish between GUI and UI and define the four types of interfaces: diegetic, spatial, meta, and non-diegetic. Additionally, you should understand some basic rules of laying out UI and how to work in different resolutions and aspect ratios.
下一章将扩展这些设计原则,并讨论设计手机游戏 UI 的一些重要考虑因素。
The next chapter will expand upon these design principles and look at some important considerations for designing UI for mobile games.
移动界面设计最具挑战性的方面之一是移动设备的宽高比数量之多。移动设备包括手机和平板电脑。手机往往比平板设备长得多,而手机上完美适配的 UI 在平板电脑上可能会重叠或看起来被挤压。除了尴尬的分辨率之外,还有一些奇怪的怪癖会影响您的 UI,例如 iPhone 上的凹口和各种三星Galaxy 设备的折叠屏。
One of the most challenging aspects of designing for mobile interfaces is the sheer amount of possible aspect ratios of mobile devices. Mobile devices include phones and tablets. Phones tend to be much longer than tablet devices, and a UI that fits perfectly on a phone may overlap or look squished when put on a tablet. On top of awkward resolutions, there are also weird quirks that will affect your UI, such as the notch in the iPhone and the folding screens of the various Samsung Galaxy devices.
移动设备还具有不同的输入和交互方式。例如,在移动设备上,您可以同时触摸屏幕上的多个位置,但在 PC 上,您每次只能用鼠标点击一个位置。移动设备需要屏幕键盘和其他外围设备来执行您在游戏机或PC 游戏上执行的相同操作。
Mobile devices also have a different set of inputs and interactions. For example, on a mobile device, you can touch the screen at multiple locations simultaneously, but on a PC, you can only click with your mouse in one place at a time. Mobile devices require on-screen keyboards and other peripherals to perform the same actions that you might perform on a console or PC game.
在本章中,我将讨论围绕移动设备各种特性的设计考虑因素,并涵盖以下主题:
In this chapter, I will discuss the design considerations around the various quirks of mobile devices and cover the following topics:
对于本章,您需要 Unity 2020.3.26f1或更高版本。
For this chapter, you will need Unity 2020.3.26f1 or later.
笔记
Note
在描述移动界面时,我将主要关注 iOS 和 Android 操作系统的手机和平板电脑。不过,我偶尔也会提到微软,因为他们确实创造了一系列平板电脑设备,尽管它们运行的是 Windows 操作系统,但确实具有触摸功能。
When describing mobile interfaces, I will mostly focus on iOS and Android operating system phones and tablets. However, occasionally I will reference Microsoft, as they do create a series of tablet devices that, despite running Windows operating systems, do have touch capabilities.
设计移动端 UI 时,你需要确保其合理且在各种分辨率下均清晰可见尺寸和长宽比。您可能还想允许不同的屏幕方向。在第 6 章中,我将讨论如何以机械方式开发可适应多种分辨率和布局的用户界面。但现在,我们先来回顾一下分辨率、宽高比和方向如何影响您的设计,以及如何使用各种屏幕设置查看您的游戏。
When you design a mobile UI, you’ll want to make sure it makes sense and is visible at various resolution sizes and aspect ratios. You also may want to allow for different screen orientations. In Chapter 6, I will discuss how you can develop user interfaces that scale to multiple resolutions and layouts, mechanically. But for now, let’s just review how resolution, aspect ratio, and orientation can affect your design and how to view your game with the various screen settings.
回想一下第 1 章,我们看过你可以更改游戏视图的分辨率和宽高比。当您将游戏的构建设置更改为 iOS 或 Android 时,将为您提供一个新的预设列表。例如,在下图中,您可以看到将构建设置为 iOS时的可能性列表:
Recall in Chapter 1, we looked at how you can change the resolution and aspect ratio of your Game view. When you change your game’s build settings to iOS or Android, a new list of presets will be provided to you. For example, in the following image, you can see the list of possibilities when the Build is set to iOS:
图 2.1:游戏视图中的 iOS 分辨率
Figure 2.1: iOS resolutions in Game view
不会显示所有可能的 iOS 分辨率,但许多常见的较新版本。您可以在https://www.ios-resolution.com/找到更完整的列表。同样,在切换到 Android 版本时,并非所有 Android 分辨率都会列出,尤其是考虑到 Android 分辨率明显更多。但是,您可以找到有关 Android 屏幕分辨率的更多信息,请参阅https://developer.android.com/guide/practices/screens_support.xhtml#testing。
It will not show all the possible iOS resolutions, but many of the common newer ones. You can find a more complete list at https://www.ios-resolution.com/. Similarly, not all Android resolutions will be listed when switching to an Android build, especially considering there are significantly more Android resolutions. However, you can find more information about Android screen resolutions at https://developer.android.com/guide/practices/screens_support.xhtml#testing.
有时,设置游戏视图的方面不足以完全看到你的 UI 将如何显示设备。例如,iPhone X 中引入的有争议的刘海在游戏视图中不显示,这可能会给您最精心设计的 UI。但是,您可以使用设备模拟器查看这个缺口以及它如何与您的 UI 重叠。
Sometimes, setting the Game view’s aspect isn’t sufficient to fully see how your UI will appear on a device. For example, the controversial notch introduced in the iPhone X doesn’t display in the Game view and can throw a huge wrench in your most carefully designed UIs. However, you can see this notch and how it overlaps with your UI using the Device Simulator.
要启用设备模拟器,请完成以下步骤:
To enable the Device Simulator, complete the following steps:
为此,请转到窗口|包管理器并在列表中搜索设备模拟器。您很可能最初不会在列表中看到它。如果没有看到它,请确保显示的包是来自Unity Registry 的包,如以下屏幕截图所示:
To do so, go to Window | Package Manager and search for Device Simulator in the list. It’s highly likely you won’t see it in the list initially. If you do not see it, make sure that the packages being displayed are those from Unity Registry, as shown in the following screenshot:
图 2.2:Unity Registry 包
Figure 2.2: Unity Registry packages
图 2.3:启用预览包
Figure 2.3: Enable Preview Packages
图 2.4:安装设备模拟器
Figure 2.4: Install Device Simulator
图 2.5:选择模拟器视图
Figure 2.5: Select Simulator view
图 2.6:模拟器上的不同设备
Figure 2.6: Different devices on Simulator
图 2.7:安全区域
Figure 2.7: Safe Area
你可能已经注意到,在图 2.1中,每个都有横向和纵向选项。分辨率。这样,您就可以根据开发过程中想要支持的方向来查看游戏。
You may have noticed in Figure 2.1 that there are both landscape and portrait options for each resolution. This allows you to view your game depending on which orientation you want to support while developing.
针对移动设备构建时,您无法指定分辨率和宽高比,而是必须支持所有分辨率和宽高比。不过,您可以在移动设备上选择屏幕方向。有两种不同的方向:横向 和纵向。
When building for mobile devices, you can’t specify resolution and aspect ratio and will instead have to support all resolutions and aspect ratios. However, you can choose between screen orientations on mobile devices. There are two different orientations: Landscape and Portrait.
宽度大于高度的游戏被称为横向分辨率。高度大于宽度的游戏被称为纵向分辨率。例如,16:9 的宽高比是横向分辨率,9:16 的宽高比是纵向分辨率,如图所示:
Games built so that they are wider than they are tall are said to have landscape resolution. Games built taller than they are wide are said to have portrait resolution. For example, a 16:9 aspect ratio would be a landscape resolution, and a 9:16 aspect ratio would be a portrait resolution, as illustrated:
图 2.8:横向与纵向
Figure 2.8: Landscape versus portrait Orientation
因此,虽然您无法选择移动游戏的确切宽高比,但您可以选择方向,从而强制宽高比变宽或变高。您可以通过导航到编辑|项目设置|播放器设置并选择移动设备来设置方向。如果您同时为 iOS 和 Android 构建,则不会必须为两者设置这些属性。从以下屏幕截图中可以看到, Default Orientation属性旁边的星号表示这些设置在多个平台之间共享:
So, while you can’t choose the exact aspect ratio your mobile game will build to, you can choose the orientation, which forces the aspect ratio to be either wider or taller. You can set the orientation by navigating to Edit | Project Settings | Player Settings and selecting the mobile device. If you are building for both iOS and Android, you will not have to set these properties for both. As you can see from the following screenshot, the asterisk next to the property of Default Orientation states that the settings are shared between multiple platforms:
图 2.9:移动设备的分辨率和方向设置
Figure 2.9: Resolution and Orientation Settings for mobile
您可以将默认方向设置为自动旋转或其他旋转之一,如下所示:
You can set the Default Orientation to either Auto Rotation or one of the other rotations, as shown:
图 2.10:移动设备的方向选项
Figure 2.10: Orientation Options for mobile
Unity 将以下方向定义为以下旋转:
Unity defines the following orientations as the following rotations:
图 2.11:移动端方向旋转
Figure 2.11: Mobile orientation rotations
当你选择除自动旋转之外的旋转作为默认方向,游戏将仅在设备上以该方向播放。如果选择自动旋转,您将可以在多个方向之间进行选择:
When you select a rotation other than Auto Rotation as the Default Orientation, the game will only play at that orientation on the device. If you select Auto Rotation, you will have the option to select between multiple orientations:
图 2.12:自动旋转选项
Figure 2.12: Auto Rotation options
在大多数情况下,最好只选择横向或纵向,而不是全部四个方向。通常,允许所有四个方向会导致游戏 UI 无法正确缩放。
In most cases, it is best to choose only the Landscape orientations or only the Portrait orientations, but not all four. Generally, allowing all four orientations will cause issues with the game›s UI’s ability to scale appropriately.
玩家往往更喜欢轮换游戏(尤其是像我一样喜欢玩当他们躺在床上玩手机充电时,手机会被迫面向一个特定的方向,因此除非你有充分的理由停止旋转,否则最好同时启用纵向和纵向上下或横向向右和横向向左。
Players tend to prefer to be able to rotate their games (especially if they’re like me and like to play games in bed while their phone is charging, thus being forced to face a specific direction), so unless you have a good reason to stop rotation, it’s a good idea to enable both Portrait and Portrait Upside Down or Landscape Right and Landscape Left.
在创建移动游戏时,几乎所有交互都是通过按钮和屏幕点击来控制的。PC 或主机游戏上尺寸合理的按钮可能太小适用于手机游戏。因此,您需要确保按钮的设计在较小的屏幕上仍然可见,并且足够大以便手指触摸。
When creating a mobile game, pretty much all of your interactions are controlled by button and screen taps. Buttons that are a reasonable size on a PC or console game may be too small for a mobile game. Therefore, you’ll want to make sure to design your buttons so that they are still visible on the smaller screen and large enough to be touched by a finger.
Apple、Google 和 Microsoft 在设计其设备时,都对按钮可触摸区域的大小有具体的建议。Apple 建议按钮为 44 点 x 44 点。Google 建议按钮为 48 dp x 48 dp,两个按钮之间有 8 dp 的间距。Microsoft 建议按钮为 9 mm x 9 mm,两个按钮之间有 2 mm 的填充。
Apple, Google, and Microsoft all have specific recommendations for the size of a button’s touchable area when designing for their devices. Apple recommends that buttons be 44 points x 44 points. Google recommends 48 dp x 48 dp with 8 dp spacing between two buttons. Microsoft recommends 9 mm x 9 mm with 2 mm padding between two buttons.
您可以在以下位置找到有关为每个移动平台设计触摸/点击区域的信息:
You can find information about designing touch/hit areas for each mobile platform at the following locations:
令人恼火的是,所有这些建议都采用不同的测量单位。那么,这些数字在设计方面意味着什么呢?你如何确保你的按钮是 9 毫米 x 9 毫米或 44 点 x 44 点?为什么他们用不同的单位来谈论这些测量值?这几乎就像他们都是竞争对手,不想很好地合作!要回答这些问题,让我们首先看看各种测量单位代表什么:
Annoyingly, all of these recommendations are in different units of measurement. So, what do these numbers even mean in terms of design? How do you make sure your buttons are 9 mm x 9 mm or 44 points x 44 points? And why are they talking about these measurements in different units? It’s almost like they are all competitors and don’t want to work nicely together! To answer these questions, let’s first look at what the various units of measurement represent:
好的,它们都代表屏幕上的物理测量单位。这样事情就简单多了。让我们将所有这些值都转换为毫米,这样我们就可以在一个更容易概念化的测量单位中比较它们。我还将把它们转换为点,因为您可以在程序(例如 Illustrator)中使用点来创建按钮艺术。
OK, so they all represent some physical unit of measurement on the screen. That makes things a little easier. Let’s convert all of these values to millimeters so we can compare them in a unit of measurement that is a bit easier to conceptualize. I’m also going to convert them to points, since you can use points in a program, such as Illustrator, to create your button art.
如果您需要转换任何这些测量单位,但又不太喜欢做数学题,那么谷歌搜索“将点转换为毫米”将会为您带来一个不错的转换计算器。
If you need to convert any of these units of measurement and you’re not too keen on the idea of doing math, googling convert points to mm will bring up a nice conversion calculator for you.
您还可以使用以下转换工具 - 它对于在所有不同的测量单位之间切换非常方便:http://angrytools.com/android/pixelcalc/
You can also use the following converter tool – it is really handy for bouncing between all of the different units of measurement: http://angrytools.com/android/pixelcalc/
在下图中,我将点的测量值四舍五入为最接近的整数,将毫米的测量值四舍五入为最接近的十分之一,以方便查看。我们可以使用此图像来比较不同的尺寸(图像已缩放,尺寸可能无法转换为其实际测量值):
In the following chart, I rounded to the nearest integer the measurement for points, and to the nearest tenth for millimeters, to make things easier. We can use this image as a way to compare the different sizes (the image has been scaled and the sizes may not translate to their real-world measurements):
图 2.13:建议的最小可点击按钮尺寸
Figure 2.13: Recommended minimum tappable button sizes
那么,你应该使用哪种尺寸?这取决于你。你不必使用他们的建议,但我个人会选择苹果的建议,因为它是最大的,因此符合另外两个建议。此外,按钮越大,越多的人能够轻松触摸它。在第 4 章中,我们将讨论如何设计我们的 UI,以便尽可能多的人能够与其交互。
So, which size should you use? It’s up to you. You don’t have to use their recommendations, but I personally go with the Apple recommendation since it is the largest and therefore meets the recommendations of the other two. Additionally, the larger the button is, the more people will be able to easily touch it. In Chapter 4, we will discuss designing our UI so that the maximum amount of people will be able to interact with it.
另一个考虑因素是,您的游戏是用拇指还是手指来玩的。如果游戏是用拇指来玩的,您需要更大的按钮,因为拇指更大!前面描述的数字是最低建议值,因此它们将用于手指点击,而不是拇指点击。
Another consideration is whether your game will be played with thumbs or with fingers. If the game will be played with thumbs, you’ll want bigger buttons because thumbs are bigger! The numbers described previously are minimum recommendations, so they would be used for a finger tap, not a thumb tap.
那么,如何确保游戏中的按钮始终是您想要的大小?Canvas Scaler组件!在第 6 章中,我们将讨论如何通过将Canvas Scaler组件的UI Scale Mode设置为Constant Physical Size来确保按钮具有指定的大小(无论分辨率如何) 。您可以选择将 Canvas 的测量单位设为毫米或点(以及其他一些单位)。
So, how do you ensure that your buttons are always the size you want in your game? The Canvas Scaler component! In Chapter 6, we will discuss how ensuring a button of a specified size, regardless of resolution, can be achieved by setting the Canvas Scaler component’s UI Scale Mode to Constant Physical Size. You have the option to have your Canvas’s measurement units be in millimeters or points (as well as a few other units).
在为移动设备进行设计时,我的建议是准备多台设备,以不同的分辨率进行测试。玩游戏,看看感觉如何。问问有比您的手小或大的手都可以玩。即使遵循了各个移动平台指定的最低准则,您仍会发现您的按钮太小,无法满足您的需要。
My recommendation when designing for mobile devices is to have multiple devices on hand to test at various resolutions. Play the game and see how it feels to you. Ask people with smaller and larger hands than yours to play. Even after following the minimum guidelines specified by the various mobile platforms, you may still find your buttons are too small for what you need.
Google 和 Microsoft 还指定了他们推荐的可见尺寸,因此只要按钮的点击区域符合推荐尺寸,您就可以使用较小的按钮图像。如果您想要一个视觉上较小但点击区域较大的按钮,请不要将按钮组件附加到小图片上,而是将其附加到较大的父点击区域,并将按钮的目标图像更改为在我的艺术中。
Google and Microsoft also specify visible sizes that they recommend, so you can have a smaller button image as long as the button’s hit area is the recommended size. If you want a button that is smaller visually but has a larger hit area, instead of attaching the button component to the tiny piece of art, attach it to a larger parent hit area and change the target image of the button to the tiny art.
许多手机游戏都只有一个输入,你可以点击屏幕上的任意位置来执行操作。例如,无尽跑酷游戏往往允许玩家点击或按住屏幕上的任意位置即可跳跃。要实现此目的,您只需必须添加一个覆盖整个屏幕的隐形按钮。如果您有另一个接收输入的 UI,则需要将其放在全屏按钮的前面,以便该按钮不会阻挡对其他UI 项的输入。
Many mobile games have a single input whereby you can tap anywhere on the screen to make an action happen. For example, endless runners tend to allow the player to tap or press and hold anywhere on the screen to jump. To achieve this, you only have to add an invisible button that covers the whole screen. If you have another UI that receives inputs, it needs to be in front of the full-screen button so that the button does not block the inputs to the other UI items.
有些游戏需要你点击屏幕的特定区域来执行特定操作。例如,我创建了我在博士论文中设计了一款名为 Sequence Seekers 的游戏。这款游戏包含一个下山模式,玩家必须点击屏幕的左侧或右侧才能在游戏中向左或向右移动。我通过添加覆盖屏幕两半的隐形按钮来实现这一点,如下所示:
Some games require that you tap on specific regions of the screen to perform specific actions. For example, I created a game called Sequence Seekers for my doctoral dissertation. This game included a down-the-mountain mode in which the player had to tap the left or right-hand side of the screen to move left or right in the game. I achieved this by adding invisible buttons that covered the two halves of the screen, as shown here:
图 2.14:使用隐形按钮创建标签区
Figure 2.14: Using invisible buttons to create tab zones
在第 9 章和第 11 章中,我们将讨论如何实现这样的按钮以及如何实现浮动方向键d 操纵杆。
In Chapter 9 and Chapter 11, we’ll discuss how to implement such buttons as well as how to implement floating D-Pads and joysticks.
在设计移动游戏时,考虑玩家如何握持设备非常重要。你不需要想要将 UI 放在玩家难以触及的区域。玩家往往喜欢用一只手握持和玩游戏。并非所有游戏都允许这样做,但如果可能的话,您希望让玩家这样做。您如何知道您的 UI 是否位于拇指可触及的区域?将 UI 放在拇指区域!本质上,拇指区域是玩家用一只手握持手机时可以轻松触及的手机区域。您可以通过握住手机并轻松移动拇指而无需移动手来找到特定手机上的拇指区域。
When designing a mobile game, it’s important to consider how the player will hold the device. You don’t want to put your UI in areas that will be difficult for the player to reach. Players tend to prefer to hold and play with one hand. Not all games allow for this, but if possible, you want to allow your players to do so. How do you know whether your UI is in an area that’s reachable by the thumb? Put the UI in the thumb zone! Essentially, the thumb zone is the area of the phone that is comfortable for the player to reach when holding the phone with one hand. You can find the thumb zone on your particular phone by holding the phone and easily moving your thumb around without having to move your hand.
以下博客文章对拇指区域进行了非常好的解释,并提供了一个方便的(无双关语)模板,用于在各种设备上查找拇指区域:https://www.scotthurff.com/posts/how-to-design-for-thumbs-in-the-era-of-huge-screens/
The following blog post offers a really great explanation of the thumb zone, along with a handy (no pun intended) template for finding the thumb zone on various devices: https://www.scotthurff.com/posts/how-to-design-for-thumbs-in-the-era-of-huge-screens/
电话链接中引用的内容有些陈旧,但它仍然是互联网上与拇指区域相关的最佳资源之一。
The phones referenced in the link are a bit on the old side, but it is still one of the best resources related to the thumb zone available on the internet.
作为一个左撇子,我恳请你在用拇指设计游戏时,考虑让游戏用左手玩起来和用右手玩起来一样容易区域。
As a lefty, I implore you to consider making the game as easy to play with the left hand as it is for the right when designing with the thumb zone in mind.
在为移动设备进行设计时,重要的是要记住,输入方式与电脑或主机游戏略有不同。移动设备上的大部分输入都是受控制的通过触摸屏、加速计或陀螺仪。这为您在创建移动游戏时提供了一组不同的设计选择。
When designing for mobile, it’s important to remember that the input works a little differently than it does with a computer or console game. Most of the input on a mobile is controlled by the touchscreen, accelerometer, or gyroscope. This opens up a different set of design choices for you when creating mobile games.
触摸屏设备通常可以访问多点触摸。您可以使用多点触摸进行不同类型的交互,但多点触摸最常见的用法是允许玩家捏合缩放。在第 8 章中,我们将讨论如何使用多点触摸输入在游戏中创建平移和捏合缩放功能。
Touchscreen devices can generally access multiple touches. You can use multi-touch for different types of interactions, but the most common usage of multi-touch allows the player to pinch-to-zoom. In Chapter 8, we will discuss how to use multi-touch input to create pan and pinch-to-zoom functionality in a game.
大多数移动设备都内置有加速度计,许多设备还配有陀螺仪。在描述它们的实际工作原理时,我们不必太过技术性,加速度计和陀螺仪之间的区别在于它们测量的内容。加速度计测量 3D 坐标系内的加速度,而陀螺仪测量旋转。我们将回顾如何使用加速度计和陀螺仪的示例参见第 8 章。
Most mobile devices have a built-in accelerometer and many also have a gyroscope. Without getting too technical in describing how they actually work, the difference between the accelerometer and the gyroscope is what they measure. The accelerometer measures acceleration within the 3D coordinate system and the gyroscope measures rotation. We will review examples of how to use the accelerometer and gyroscope in Chapter 8.
如果你在为移动设备制作 UI 时,您可能希望使用特定于设备的 UI 元素来保持一致的风格。您可以在以下位置找到用于为每个移动平台设计 UI 的各种艺术资产和模板地点:
If you are making a UI for a mobile device, you may want to use the device-specific UI elements to maintain a consistent style. You can find various art assets and templates for designing UI for each mobile platform at the following locations:
为移动设备创建 UI 与为游戏机或电脑创建 UI 并无太大区别,但不同之处在于,您可以接受多个屏幕输入,还可以访问有关设备加速度计和陀螺仪的信息。此外,分辨率在游戏开发中起着重要作用,因为移动设备的分辨率和宽高比范围非常广泛。
Creating a UI for mobile devices isn’t too different from creating a UI for a console or computer, but it is different in that you can accept more than one screen input and can also access information about the device’s accelerometer and gyroscope. Additionally, resolution plays an important part in your game’s development, since mobile devices have a huge range of resolutions and aspect ratios.
在下一章中,我们将讨论为 XR 应用程序(包括 VR、MR和 AR)开发 UI 的设计注意事项。
In the next chapter, we’ll discuss the design considerations for developing UI for XR applications, including VR, MR, and AR.
虚拟现实( VR )、混合现实( MR ) 和增强现实( AR ) 的用户界面( UI ) 设计研究范围广泛且不断发展。它是一项新兴技术,也是实践尚未完全界定;然而,有很多研究人员和开发人员在工作努力确定哪些是最佳做法。在此在本章中,我将总结一些普遍认同的为扩展现实( XR ) 体验设计 UI 的最佳实践。
The study of user interface (UI) design for virtual reality (VR), mixed reality (MR), and augmented reality (AR) is expansive and ever-growing. It is an emergent technology, and best practices are not yet fully defined; however, there are a lot of researchers and developers working diligently to determine what those best practices are. In this chapter, I’ll summarize some of the generally agreed-upon best practices for designing a UI for extended reality (XR) experiences.
在本章中,我将讨论以下内容:
In this chapter, I will discuss the following:
在我们可以开始讨论VR、MR和AR的最佳实践,我们应该明确XR、VR、MR和AR的定义。
Before we can begin discussing best practices for VR, MR, and AR, we should clarify the definition of XR, VR, MR, and AR.
如果你仔细阅读本章,你可能会注意到,没有标题为“为 XR 设计 UI”的章节。这是因为 XR 涵盖了 VR、MR 和 AR!这是一个总称。事实上,本章更简洁的标题可能是“设计 XR UI”!XR 包括 VR、MR 和AR 技术:
You may notice, if you look ahead on this chapter, that there are no sections titled Designing UI for XR. That is because XR encompasses VR, MR, and AR! It is an umbrella term. In fact, a more concise title for this chapter could have been Designing XR UI! XR includes VR, MR, and AR technologies:
图3.1:XR技术的代表
Figure 3.1: Representation of XR technologies
VR体验可能是最容易描述的。它们完全存在于虚拟世界中。整个物理世界都被遮挡,你唯一能看到的就是虚拟空间。这种空间通常非常身临其境,以至于大脑很难将其与现实区分开来。支持 VR 的设备可以覆盖用户的眼睛,完全阻挡现实世界中的视觉效果。如今支持 VR 的热门设备有 Meta(正式名称为 Oculus)Quest 系列、索尼 PlayStation VR 系列和 HTC Vive 系列。一些流行的 VR 游戏包括《半条命:Alyx》和《Beat Saber》。
VR experiences are perhaps the easiest to describe. They exist fully in the virtual world. The entire physical world is blocked from view, and the only thing you can see is the virtual space. This type of space can often be so immersive that the brain struggles to distinguish it from reality. Devices that facilitate VR fit over the user’s eyes to completely block out visuals in the real world. Popular devices today that support VR are the Meta (formally Oculus) Quest series, the Sony PlayStation VR series, and the HTC Vive series. Some popular examples of VR games include Half-Life: Alyx and Beat Saber.
MR叠加虚拟现实世界中的物品并允许您与它们互动。用户可以看到现实世界并与现实世界的物品和虚拟物品互动。MR 设备也可以以眼镜和耳机的形式戴在用户的脸上。它们要么不会完全阻挡用户的视线,要么允许视频直通,让用户通过镜头看到现实世界。如今,促进 MR 练习的流行设备有 Meta Quest 2(及以上)、Microsoft HoloLens 系列和 Magic Leap。MR 体验的一个流行示例是I Expect You To Die:Home Sweet Home。
MR overlays virtual items in the real world and allows you to interact with them. The user can see the real world and interact with both real-world items and virtual items. MR devices are also worn on the user’s face in the form of glasses and headsets. They either do not fully block out the real world from the user’s view or they allow video passthrough to allow the user to see the real world through lenses. Popular devices today that facilitate MR exercises are the Meta Quest 2 (and above), the Microsoft HoloLens series, and Magic Leap. A popular example of an MR experience is I Expect You To Die: Home Sweet Home.
AR类似于MR 结合了现实世界和虚拟世界;然而,它的不同之处在于虚拟物品不会与现实世界互动。在 AR 中,虚拟物品只是覆盖在现实世界上,并不出现在与现实世界相同的空间中。此外,它不允许用户以感觉就像发生在他们自己的世界中的方式与虚拟物品互动。AR 互动通常发生在屏幕上,而 MR 互动发生在物理空间内。两者的区别很微妙,但你可以认为 AR 是与屏幕上出现的虚拟物品互动,而 MR 是与看起来像在你的空间中的物品互动。市场上有更多 AR 设备。智能手机和平板电脑是最受欢迎的 AR 推动者;然而,一些智能眼镜也能做到这一点。此外,世界各地都有 AR 屏幕、信息亭和装置。AR 游戏的流行例子包括Pokémon GO 和Ingress。
AR is similar to MR in that it combines real and virtual worlds; however, it differs in that the virtual items do not interact with the real world. In AR, virtual items are simply overlaid on the real world and don’t appear within the same space as the real world. Also, it does not enable the user to interact with virtual items in a way that feels like it is happening in their world. AR interactions usually happen on a screen, while MR interactions happen within a physical space. The distinction is subtle, but you can consider AR as interacting with virtual items that appear on a screen while MR is interacting with items that appear as if they are in your space. There are significantly more AR devices available on the market. Smartphones and tablets are the most popular facilitators of AR; however, some smart glasses also do it. Additionally, there are AR screens, kiosks, and installations that can be found all over the world. Popular examples of AR games include Pokémon GO and Ingress.
笔记
Note
XR 的概念和区别有些微妙,我还没有完全深入研究在本节中。如果您想了解更多信息,我建议您研究虚拟连续体:https ://www.interaction-design.org/literature/topics/virtuality-continuum 。
There is some nuance to the ideas of XR and their distinctions that I have not fully delved into in this section. If you would like to learn more, I recommend researching the virtuality continuum: https://www.interaction-design.org/literature/topics/virtuality-continuum.
现在我们了解了 VR、MR 和 AR 之间的区别,让我们来看看为这些体验设计 UI 的一些最佳实践。
Now that we understand the difference between VR, MR, and AR, let’s look at some best practices for designing UI for these experiences.
通常,当你想到 UI 时,你会想到平视显示器( HUD )——屏幕上显示的 UI所有游戏玩法都离不开屏幕。但在 VR 中,没有像在游戏机上玩的游戏那样的屏幕。玩家感觉就像身临其境一样。完全沉浸在这个世界中,他们通过屏幕(镜头)体验 VR 游戏时,屏幕上的某些东西会变得模糊,无法看到,因为它正好位于他们眼睛上方。因此,VR UI 往往被放置在玩家沉浸其中的世界中。
Normally, when you think of UI, you think of a heads-up display (HUD)—UI that is on the screen in front of all gameplay. But in VR, there is no screen in the same sense as there is in games played on a console. The player feels as if they are fully immersed in the world, and putting something on the screen they are experiencing a VR game through (the lenses) would result in a fuzzy, unviewable blur since it would be right on top of their eyes. Due to this, VR UI tends to be placed within the world the player will be immersed in.
笔记
Note
在第 16 章中,我们将讨论在世界中创建具有物理表示的 UI 的步骤。
In Chapter 16, we will discuss steps around creating UI with a physical representation within the world.
虽然可能存在一些例外,但 VR 游戏中的 UI 通常放置在三个位置:
While there may be some exceptions, there are three locations where a UI in a VR game is usually placed:
哪一个您选择的三个展示位置取决于游戏设计会影响游戏体验,但也会受到你是否希望玩家能够与 UI 交互以及如何交互的影响。本质上,你要确保玩家能够看到 UI 并与之交互(如果需要)。
Which of the three placements you choose depends on your game’s design but can also be affected by if and how you want the player to be able to interact with the UI. In essence, you want to make sure the player can see the UI and interact with it (if necessary).
让我们从设计视觉 UI 的考虑开始我们的 VR UI 设计探索。
Let’s start our VR UI design exploration with considerations for designing visual UI.
正如我之前所说,设计 VR 游戏体验并没有一套明确的规则因为该技术仍被认为是新兴技术,研究人员和开发人员仍在学习最佳实践是什么。
As I’ve said before, there is not an explicit set of rules for designing VR gameplay experiences as the technology is still considered emergent and researchers and developers are still learning what those best practices are.
创建时如果 UI 始终与玩家保持特定距离,则将其放置在玩家可以看到的地方非常重要。玩家可以看到物品并不意味着看到它会很舒服。例如,虽然玩家可以看到其周边范围内的物品,但这不是放置物品的舒适位置UI。下图显示了人的视野( FOV ) 的一般角度,其中灰色区域为最大视角:
When creating a UI that will be a specific distance from the player at all times, it is important to put it in a place that the player can see. Just because a player can see an item doesn’t mean it is comfortable to see it. For example, while a player can see items in their peripheral range, this would not be a comfortable place to put the UI. The following diagram shows the general angle for a person’s field of view (FOV), where the gray areas are maximum viewing angles:
图 3.2:人类视野的概括
Figure 3.2: Generalization of the human field of view
笔记
Note
上图中描述的角度是一般准则,并非一成不变。它们会根据用户的视力、是否要要求他们转动眼睛(而不是头部)以及所使用的设备而变化。
The angles described in the previous diagram are general guidelines and not set in stone. They will change depending on the user’s eyesight, whether or not you want to require them to rotate their eyes (and not their head), and the device you are using.
记住当玩家移动头部时,摄像头也会随之移动。因此,如果你的 UI 被放置在鼓励玩家转动头部的位置头部并锚定在头部,UI 将随头部移动。因此,请尝试将任何锚定 UI 放置在图 3.2中描述的区域中。
Keep in mind that when a player moves their head, the camera moves with it. So, if your UI is placed in a position that encourages them to rotate their head and is anchored to their head, the UI will move with their head. So, try to place any anchored UI in the area described in Figure 3.2.
除了相对于玩家眼睛的角度之外,您还必须考虑与玩家眼睛的距离。如果太近或太远,都会引起眼睛疲劳。尽量让任何视觉 UI(尤其是必须阅读的文本)距离玩家眼睛 1.3 到 3 米以内。
On top of the angle relative to the player’s eye, you must also consider the distance from the player’s eye. If it’s too close or too far, it will cause eye strain. Try to keep any visual UI, especially text that must be read, within 1.3 to 3 in-game meters from the player’s eyes.
如果玩家处于静止位置,则在距离他们一定距离的地方创建静态弯曲 UI 总是可以帮助玩家舒适地移动头部来查看UI。
If the player is in a stationary position, creating a static curved UI a set distance away from them always helps the player comfortably move their head around to view the UI.
我想讨论的最后一个关于视觉 UI 的考虑因素是文本大小。虽然建议可能因来源而异,但我看到的一致引用(没有双关语)是,UI 应以每米 2.32 厘米的高度显示。因此,如果它显示在 1 米外,它应该显示 2.32 厘米高。如果它显示在 2 米外,它应该显示 4.64 厘米高。就我个人而言,我的视力很差,我倾向于让我的 UI 文本比这大一点,这样我就可以轻松查看。我的建议是让多个眼力不同的人玩你的游戏,并告诉你 UI 是否舒适。
The last consideration for visual UI I want to discuss is text size. While the recommendation may vary depending on your source, the one I see consistently cited (no pun intended) is that UI should appear a multiple of 2.32 cm tall for every meter away the UI displays. So, if it displays 1 meter away, it should appear 2.32 cm tall. If it displays 2 meters away, it should appear to be 4.64 cm tall. Personally, I have very bad eyesight, and I tend to make my UI text a bit bigger than this so I can easily view it. My recommendation is to have multiple people with varying eye strengths play your game and tell you if the UI is comfortable or not.
现在我们已经回顾了在 VR 空间中设计视觉 UI 的注意事项,让我们回顾一下可交互 UI 的一些注意事项。
Now that we’ve reviewed considerations for designing visual UI in VR space, let’s review some considerations for interactable UI.
在 VR 中,可交互 UI 可以通过几种不同的方式实现,这取决于体验使用控制器或手部追踪。
Interactable UI is achieved in a few different ways in VR, and it depends on whether the experience uses a controller or hand tracking.
When using a controller, here are common ways in which UI can be interacted with:
使用手部追踪时,UI 可以采用以下常见方式进行交互:
When using hand tracking, here are common ways in which UI can be interacted with:
当通过射线与 UI 交互时,玩家不需要物理上能够接触到它。但如果你想创建玩家的化身必须虚拟交互的 UI,你将需要确保它在玩家触手可及的范围内水平和垂直方向均可。可达性因素包括:
When interacting with UI via a ray, it is not necessary for the player to physically be able to reach it. But if you want to create UI that the player’s avatar must virtually interact with, you will need to make sure it is within reach of the player both horizontally and vertically. Factors for reachability include the following:
一个好的经验法则是,将物品放置在距离玩家静止位置 0.5 到 0.75 米之间,以确保玩家能够够到物品。就像您要让视力水平不同的人测试您的视觉 UI 一样,您也需要让身高和能力不同的人测试您的 UI。
A good rule of thumb is to try to make sure players can reach your items by placing them between 0.5 and 0.75 meters from their stationary position. Just as you want to test your visual UI with people with various levels of sight, you will want to test your UI with people of various heights and abilities.
无论是使用控制器还是手势追踪,您都需要确保所选的互动方式不会让玩家感到疲劳。要求他们做出多个手势或长时间举起双手可能会导致疲劳和紧张,而您肯定不希望玩家承受这种压力。
With both controllers and hand tracking, you want to make sure that whatever interaction you choose is not tiring for the player. Requiring them to perform multiple gestures or hold their hands up for a significant amount of time can cause fatigue and strain that you don’t want to inflict on your player.
这就是我想讨论的关于为 VR 设计 UI 的主要考虑因素。但是,还有更多的研究和信息可供您探索!有关为 VR 设计 UI 的更多信息,我建议查看本章末尾提供的资源。
That concludes the major considerations for designing UI for VR that I wish to discuss. However, there is a lot more research and information out there for you to explore! For more information on designing UI for VR, I recommend checking out the resources provided at the end of the chapter.
现在我们已经了解了为 VR 设计 UI,接下来让我们了解一下为 MR 设计 UI。
Now that we’ve looked at designing UI for VR, let’s look at designing UI for MR.
为 MR 设计 UI 与为 VR 设计 UI 非常相似,因此上一节中讨论的大部分内容都适用。不过,为 MR 设计 UI 确实有一些额外的注意事项。
Designing UI for MR is extremely similar to designing UI for VR, so most of what I discussed in the previous section translates. However, designing UI for MR does have a few extra caveats.
主要VR 和 MR 的 UI 设计之间的区别在于 MR 结合了玩家的物理空间和他们周围的物品。在玩 VR 时,人们通常被假设周围有宽阔的空间——这样当他们胡乱挥舞时,就不会撞到任何东西而伤到自己。MR 体验则恰恰相反——玩家通常被鼓励在现实世界中接触家具、墙壁和其他物品。
The main distinction between designing UI for VR and MR is MR incorporates the player’s physical space and items around them. When playing VR, people generally are assumed to have a wide-open space around them—so that when they flail about recklessly, they don’t hit anything and hurt themselves. The opposite tends to be true with MR experiences—players are generally encouraged to be around furniture, walls, and other items in their real world.
虚拟放置的 UI 不仅必须能够通过玩家访问,还不能与玩家世界中存在的事物相交。您不能让玩家按下桌子内的按钮或查看墙壁后面的屏幕。因此,在为 MR 体验设计 UI 时,放置 UI 时必须考虑玩家的空间,而不仅仅是玩家的身体。
The UI being placed virtually not only has to be accessible via the player, but it also cannot intersect with things that exist within their world. You can’t have the player pressing buttons that are inside their desk or viewing screens that are behind their walls. So, when designing UI for an MR experience, you must consider the player’s space and not just the player’s body when placing your UI.
解决此问题的方法包括允许玩家选择其 UI 的显示位置、将其显示在技术检测到的表面上(如何检测在很大程度上取决于玩家使用的设备),或将其附加到玩家身上。
Ways around this include allowing the player to pick where their UI displays, displaying it on top of surfaces that the technology detects (how it is detected is highly dependent on the device the player is using), or attaching it to the player.
MR 是一个比 VR 更为新兴的领域,尤其是在视频游戏领域。到目前为止,MR 主要用于工业和医疗目的,并且通常在高度受控的空间中进行。对于在家中拥有无限家具和物体摆放可能性的游戏玩家来说,MR 仍处于起步阶段,直到最近才可通过 Microsoft HoloLens 在 Unity 中实现。然而,Meta Quest 2 增加了在 Unity 中创建 MR 游戏的功能,而 Meta Quest 3 计划提供更多精心策划和完善的 MR 支持。此外,Magic Leap 已与 Unity 合作,以支持 MR 开发。因此,未来几年您可能会看到更多有关 MR UI 的最佳实践和技术。
MR is an even more emergent space than VR, especially in the video game field. MR has mostly been used for industrial and medical purposes up to this point and is usually performed in a highly controlled space. MR for a gamer in their home with infinite possibilities of furniture and object placement is still in its infancy and until recently was only achievable in Unity via the Microsoft HoloLens. However, the Meta Quest 2 has added the ability to create MR games in Unity, and Meta Quest 3 plans to have more curated and polished MR support. Additionally, Magic Leap has partnered with Unity to allow for MR development. So, you will likely see more best practices and technology around MR UI in the coming years.
笔记
Note
有关使用 Unity 和 HoloLens 进行开发的信息,请参阅https://learn.microsoft.com/en-us/windows/mixed-reality/develop/unity/unity-development-overview?tabs=arr%2CD365%2Chl2。
For information about developing with Unity and the HoloLens, see https://learn.microsoft.com/en-us/windows/mixed-reality/develop/unity/unity-development-overview?tabs=arr%2CD365%2Chl2.
信息有关 Quest 3 计划的 MR 功能,请参阅https://developer.oculus.com/blog/build-the-next-generation-of-vr-mr-with-meta-quest-3/。
For information about MR capabilities planned for Quest 3, see https://developer.oculus.com/blog/build-the-next-generation-of-vr-mr-with-meta-quest-3/.
信息有关使用 MagicLeap 进行开发的信息,请参阅https://ml1-developer.magicleap.com/en-us/learn/guides/unity-overview。
For information about developing with MagicLeap, see https://ml1-developer.magicleap.com/en-us/learn/guides/unity-overview.
遗憾的是,由于 MR 是 Unity 引擎中一个新兴领域,我无法透露更多关于为其设计 UI 的信息。不过,AR 已经存在了相当长一段时间,所以现在让我们来探索一下吧!
Sadly, since MR is such a new and emergent space within the Unity engine, I cannot say much more about designing UI for it. However, AR has been around for quite some time, so let’s explore it now!
请记住,AR 与 MR 的区别在于,AR 往往出现在覆盖MR 更像真实世界,而 MR 则更像沉浸式,在现实世界中。因此,AR 体验中的互动项目将显示在屏幕上,而不是在现实世界中。
Remember, that what distinguishes AR from MR is AR tends to be on a screen that overlays the real world, while MR appears more immersed and inside the world. So, interactive items for AR experiences will be displayed on the screen, not within the world.
设计AR 的 UI 高度依赖于用于增强现实的设备。为了清晰起见,我将重点讨论为手机开发的 AR 游戏,而不是试图泛泛而谈,包括分期付款和自助服务终端等内容。
Designing UI for AR is highly dependent on the device that is being used to augment reality. For the sake of clarity, I am going to focus my discussion on AR games developed for cell phones rather than trying to speak universally and include things such as installments and kiosks.
在移动设备或触摸屏上为 AR 应用设计 UI 时,您需要遵循第 2 章中概述的规则。最佳做法是将大多数非增强数字项目(即 HUD UI)放置在屏幕的外边缘。然后,增强数字项目将出现在屏幕中间。这将导致增强的项目让世界成为焦点。
When designing UI for AR app on a mobile or touchscreen, you will want to follow the rules outlined in Chapter 2. Best practice is to place most non-augmented digital items (that is, the HUD UI) on the outer edges of the screen. Then, augmented digital items will appear in the middle of the screen. This will cause items that are augmenting the world to be the focus.
在本章中,我们讨论了开发 XR 体验的一些基本设计注意事项。由于我们体验 VR、MR 和 AR 的方式各不相同,因此我们回顾了在设计每种类型的体验时应牢记的注意事项。
In this chapter, we discussed some basic design considerations for developing XR experiences. Since the ways in which we experience VR, MR, and AR are all different, we reviewed considerations you should keep in mind when designing for each of these types of experiences.
XR 仍是一个新兴领域,因此目前还没有很多被广泛接受的标准。MR 尤其如此,最近才开始推出面向消费者的 MR 设备。不过,本章提供的信息应该可以帮助您开始进行 XR UI 设计,并让您开始思考 XR 空间与屏幕空间的区别。
XR is still an emergent space, so there are not yet many widely accepted standards. This is particularly true with MR, which just recently began having consumer-available MR devices. However, the information provided in this chapter should help you get started with XR UI design and get you started thinking about how the XR space differs from screen space.
在下一章中,我们将讨论通用设计和可访问性的概念,以及如何使您的用户界面更具包容性和用户友好性。
In the next chapter, we will discuss the concepts of universal design and accessibility and how you can make your user interfaces more inclusive and user-friendly.
您可以在此处找到有关学习如何为 XR 设计 UI 的更多资源:
Further resources for learning about designing UI for XR can be found here:
在设计界面时,您需要考虑可能与您的游戏互动的所有不同类型的玩家。您需要考虑他们的体型、年龄、当前情况、周围位置、输出设备、输入设备、认知、偏好、移动性水平等。您希望您的界面可供尽可能多的人使用。
When designing your interface, you want to consider all the different types of players who may interact with your game. You want to consider their size, their age, their current situation, their ambient location, their output device, their input device, their cognition, their preferences, their mobility levels, and more. You want your interface to be usable by as many types of people as possible.
在设计过程开始时考虑这些因素非常重要,因为在最初设计 UI 时考虑这些因素比在 UI 已经设计、构建和实现后再加入它们要容易得多。
It’s important to consider these factors at the beginning of the design process as designing your UI initially with these things in mind is significantly easier than incorporating them later when your UI is already designed, built, and implemented.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
Ronald Mace coined the term universal design and defined it as follows:
通用设计是设计可供所有人使用的产品的概念,无论其年龄、体型、偏好、能力、残疾、状况或情况如何。它不专注于为特定人群设计,而是专注于设计可供最广泛人群使用的产品。通用设计的概念最初侧重于建筑,现已扩展到包括所有类型产品的设计,包括视频游戏。无障碍设计是一种专门针对特定人群的设计注重有障碍和残疾的人的可用性,是通用设计的一个子集。
Universal design is the concept of designing products that are usable by all people regardless of age, size, preference, ability, disability, condition, or situation. It does not focus on designing for a specific group of people but instead focuses on designing products that are useable by the widest range of people. While initially focused on architecture, the concepts of universal design have been expanded to include the design for all types of products, including video games. Accessible design is a design that specifically focuses on usability by those with impairments and disabilities and is a subset of universal design.
为了使设计有效,必须做出有目的的决定,以确保设计既实用又对所有类型的人都有用。在设计用户界面时,您应该从设计过程一开始就考虑如何让所有人都能使用和访问它。如果您从一开始就考虑到这些因素,那么设计一个普遍可用且可访问的用户界面要比在现有界面上改造功能容易得多。
For a design to be effective, purposeful decisions must be made to ensure that the design is both functional and useful to all types of people. When designing a user interface, you should consider how to make it usable and accessible to all people from the beginning of the design process. It is much easier to design a universally usable and accessible UI if you start with these considerations in mind than it is to retrofit features on top of pre-existing interfaces.
在以下章节中,我将讨论使用通用设计原则设计用户界面的注意事项,以及使您的界面可访问应采取的特殊注意事项。
In the following sections, I will discuss considerations for designing user interfaces using the principles of universal design as well as the special considerations you should take to make your interface accessible.
由于本书主要介绍 Unity 中的 UI 开发(它主要是一个视频游戏引擎),因此我将描述的大多数示例和用例都与视频游戏相关。
Since this book is on the development of UI within Unity, which is primarily a video game engine, the majority of the examples and use cases I will describe will be related to video games.
1997 年,北卡罗来纳州立大学的一个工作组制定了通用设计的 7 项原则。这些原则旨在帮助指导产品设计,以便它们能够具有普遍适用性。这些原则是根据不同类型的设计正式定义的,重点是架构。我将根据每个原则在数字用户界面中的应用对其进行解释,然后提供每个原则如何在视频游戏界面设计中使用的例子。如果您想查看原则列表及其具体指南,可以访问https://universaldesign.ie/what-is-universal-design/the-7-principles/。
In 1997, a working group at North Carolina State University developed the 7 Principles of Universal Design. These principles are meant to help guide the design of products so that they can be universally usable. These principles are formally defined in terms of different types of designs with an emphasis on architecture. I will paraphrase each principle in terms of its application to digital user interfaces and then provide examples of how each principle can be used in video game interface design. If you’d like to view the list of principles and their specific guidelines, you can visit https://universaldesign.ie/what-is-universal-design/the-7-principles/.
许多原则是重叠的,我将提供的一些示例可能适用于多项原则。
Many of these principles overlap and some of the examples I will provide could apply to multiple principles.
公平的使用原则指出,设计应该同样可用,对所有人都有吸引力。如果不可能为所有人提供相同的体验,那么他们应该拥有同等的体验。界面应该设计成对所有用户都有吸引力,不应该歧视或隔离用户。这是第一原则,因为它最终推动了所有其他原则。
The equitable use principle states that a design should be equally usable and appealing to all. If an identical experience is not possible for all, they should have an equivalent experience. An interface should be designed to appeal to all users and should not stigmatize or segregate users. This is the first principle because it ultimately drives all the other principles.
例如,界面中应使用高对比度的颜色。高对比度的界面不仅对所有用户都有吸引力,而且还可以帮助坐在阳光直射下的移动用户看清界面,避免让视力低下和色盲等视觉障碍用户感到不快。
For example, high-contrasting colors should be used in interfaces. High-contrasting interfaces are not only appealing to all users but they help mobile users sitting in direct sunlight see your interface and they avoid stigmatizing users with visual impairments such as low vision and color blindness.
请记住,通用设计的目标并不是明确地为无障碍设计(即为残障群体设计)。使用高对比度配色方案之类的东西对所有人都有好处,而不仅仅是那些有视力障碍的人。它也不是明确需要打开的设置,例如打开色盲模式,这就是无障碍设计。我将在本章后面讨论无障碍设计。
Remember that the goal of universal design is not explicitly about designing for accessibility (that is, designing for groups with disabilities). Utilizing something like a high-contrasting color scheme is beneficial to all, not just those with visual impairments. It is also not a setting that explicitly needs to be turned on, such as turning on a color blind mode, which would be designing for accessibility. I’ll discuss designing for accessibility later in this chapter.
如果您正在制作 PC 游戏,您不会希望隐藏信息以便只能通过鼠标访问。允许通过控制器上的方向键导航到信息或通过键盘按 Tab 键导航到信息将允许使用不同输入设备(由于个人偏好或视力障碍)的用户访问与鼠标用户相同的信息。
If you’re making a PC game, you don’t want to hide information so that it is only accessible by a mouse. Allowing information to be navigated to via the d-pad on a controller or tabbed to by a keyboard will allow users who are using different input devices (due to personal preference or vision impairments) to access the same information as a mouse user.
灵活使用原则规定,具有广泛偏好和能力的人应该设计应能适应用户的需求,并应为用户提供如何使用设计的选择。这一原则的关键是为用户提供与游戏互动的选择。
The flexibility in use principle states that people with a wide range of preferences and abilities should be accommodated by the design and people should be provided choice in how they use the design. The key to this principle is providing users with choices when it comes to interacting with your game.
如果您正在制作一款移动游戏,则可以提供左右手模式,即交换按钮在屏幕哪一侧的位置,就像我在游戏《Barkeology》中所做的那样,如下图所示。这样,玩家就可以轻松地与界面互动,而不会用惯用手阻碍游戏玩法。
If you’re making a mobile game, you can provide left and right-handed modes that swap which side of the screen the buttons are on, as I did in my game Barkeology, which is shown in the following figure. This allows players to easily interact with the interface without blocking the gameplay with their dominant hand.
图 4.1:iOS 版 Barkeology 中的左手模式与右手模式
Figure 4.1: Left-handed mode versus right-handed mode in Barkeology for iOS
Fantasy Life Online 让你在三个位置中选择一个放置“愤怒按钮”的位置——当玩家可以使用特殊攻击时,按钮就会出现。这允许玩家可以根据自己的游戏风格和握持手机的方式选择最舒服的位置。
Fantasy Life Online lets you choose between three locations for the placement of the “fury button” – a button that appears whenever a special attack is available to the player. This allows the player to pick a location that is most comfortable to them based on their playstyle and the way they hold their phone.
对于 PC 游戏,您可以允许玩家选择不同的输入设备。例如,您可以提供使用键盘和鼠标或控制器玩游戏的选项。您可以允许玩家随意映射键盘或控制器,或者为他们提供预定义的方案供他们选择。允许玩家选择控制器按钮方案也适用于游戏机。
For PC games, you can allow players to choose different input devices. For example, you can provide the option to play with a keyboard and mouse or a controller. You can allow players to map the keyboard or controller however they wish or give them predefined schemes to choose from. Allowing players to choose controller button schemes is also applicable to consoles.
如果您的游戏有文字,请允许用户更改文字的大小或显示速度。
If your game has text, allow users to change the size or the speed at which it is presented to them.
还有许多其他例子,说明您可以为界面提供灵活性。在设计界面时,请务必考虑玩家可能具有的不同偏好,以便您可以相应地布局界面并映射输入。
There are many more examples of ways in which you can allow flexibility in your interfaces. When designing your interfaces, just be sure to consider the different preferences your players may have so that you can lay out your interface and map your inputs accordingly.
简单直观的使用原则指出,设计应该易于人们理解,无论他们过去的知识、经验、技能或语言水平如何。
The simple and intuitive use principle states that a design should be easy to understand for people, regardless of their past knowledge, experience, skill, or language level.
不要你的界面过于复杂。如果你必须解释它,那么它可能需要重新设计。从未玩过电子游戏的玩家应该能够像老手一样轻松理解你的界面。
Don’t make your interfaces overly complicated. If you have to explain it, it probably needs to be redesigned. Players who have never played a video game before should be able to understand your interface just as easily as a veteran.
将最重要的信息放在最显眼的位置,使它们最容易找到。不要将常用功能隐藏在多次点击按钮和菜单后面。如果您的菜单系统是嵌套的,并且特定菜单的访问频率高于其他菜单,请考虑将它们映射到热键或按钮。
Place the most important information in the most visible locations and make them the easiest to find. Don’t hide common functionality behind multiple button clicks and menus. If your menu system is nested and particular menus are accessed more frequently than others, consider mapping them to hotkeys or buttons.
向用户提供反馈,让他们知道他们何时与你的界面交互。如果你有屏幕按钮,可以通过点击或鼠标单击来交互,那么让它们向用户提供反馈,让他们知道他们何时突出显示或单击了它们。
Provide feedback to the user to let them know when they are interacting with your interface. If you have on-screen buttons meant to be interacted with by a tap or mouse click, have them provide feedback to the user to let them know when they have highlighted them or clicked on them.
向用户提供提示,告知玩家界面的哪些部分可以交互。您可以通过设计看起来可按的按钮或使用动画或配色方案吸引用户对项目的注意来实现这一点。
Provide prompts to the user that signal to the player which parts of your interface are interactable. You can accomplish this by designing buttons that look physically pressable or by drawing attention to items with animations or color schemes.
重要的是要记住,您的用户会有不同的阅读水平,并且可能会说不同的语言。尽可能使用更多的图标隐喻来减少对文本的依赖。隐喻是其含义被广泛认可的符号。例如,大多数人会认识到下图中显示的按钮的含义:播放、暂停、菜单、设置、关闭/取消、确认、静音和保存:
It’s important to remember that your users will have various reading levels and may speak different languages. Reduce your dependency on text by using more icon metaphors when possible. Metaphors are symbols whose meanings are widely recognized. For example, most people will recognize the meaning of the buttons shown in the following figure as play, pause, menu, settings, close/cancel, confirm, mute, and save:
图 4.2:界面隐喻的示例
Figure 4.2: Examples of interface metaphors
用文字伴随图标隐喻,以便用户有多种方式来感知特定图标的含义。
Accompany the icon metaphors with text so that users have multiple ways of perceiving what specific icons mean.
完全删除游戏 UI 中的所有文本并用图标和图像替换并不总是可行的。因此,将游戏翻译成多种语言可以提高 UI 的普遍可感知性。在第 11 章中,我将讨论在构建 UI 时可以做的事情,以使翻译过程更加顺利,并介绍创建 UI翻译系统的示例。
It’s not always possible to completely remove all text from your game’s UI and replace it with icons and imagery. So, translating your game into multiple languages could increase your UI’s universal perceptibility. In Chapter 11, I will discuss things you can do while building your UI to make the translation process go more smoothly, as well as cover an example of creating a UI translation system.
可感知信息原则指出,无论用户所处的环境或感知能力如何,信息都应以可感知的方式传达给用户。在考虑这一原则时,您需要考虑以其他方式传达信息可以传达的内容以及如何在多种情况下清晰地传达。请记住,界面是玩家感知和与游戏互动的镜头。因此,他们必须能够理解它。
The perceptible information principle states that information should be conveyed to users in a perceptible way regardless of the environment they are in or their sensory abilities. When considering this principle, you want to think of alternate ways in which information can be conveyed and how you can clearly convey it in multiple scenarios. Remember that the interface is the lens through which the player perceives and interacts with your game. So, they must be able to understand it.
UI 的颜色应该与背景相比更加突出,但也不能太突出,以免造成眼睛疲劳。游戏没有特定的配色方案,但一般来说,分割互补配色方案最适合减少眼睛疲劳,同时还能产生足够的对比度,使物品易于区分。
The colors of the UI should stand out compared to the background, but it should also not stand out so much that it causes eye strain. There is no specific color scheme you have to use for your game, but as a general rule, split complementary color schemes are the best for reducing eye strain while also producing enough contrast to make items distinguishable.
高对比度的文本在不同光线下更容易被看到。确保文本大小合适,以便用户清晰可见,并允许根据用户的偏好调整文本大小。此外,选择清晰易读的字体。
High-contrast text will make it easier to see in different lighting. Make sure the text is appropriately sized so that it’s visible and allow the text to be resized to the user’s preference. Also, choose fonts that are clear and easy to read.
画外音不仅仅适用于对话!您还可以为菜单提供画外音。例如,《孤岛惊魂 6》会自动用语音读出开始屏幕上的各种菜单选项,以便有视力障碍的人仍能感知屏幕上的项目。此选项默认开启,不需要用户与菜单交互即可访问。
Voiceover isn’t just for dialogue! You can provide voiceover for your menus as well. Far Cry 6, for example, has an automated voice read out the various menu options on the start screen to allow those with visual impairments to still perceive the items on the screen. This option is turned on by default and does not require users to interact with a menu to access it.
如果您正在制作主机游戏,请考虑玩家会使用不同类型的电视,这些电视的分辨率和亮度设置也不同。启动 PlayStation 或 Xbox 游戏时,您可能会看到提示,提示您调整亮度直到图像可见,或者提示您移动方框的角直到它们到达屏幕边缘。这些提示是确保无论玩家使用哪种电视,游戏都能正确显示的安全措施。
If you’re making a console game, consider the fact that your players will have different types of TVs with different resolutions and brightness settings. Upon starting a PlayStation or Xbox game, you may have seen a prompt to adjust the brightness until an image was visible or maybe saw a prompt to move the corners of a box until they reached the edge of your screen. These prompts are safeguards to ensure that the game appears correctly to players regardless of their TV.
容错原则规定,应尽量减少与设计交互的不良后果。我们都曾不小心保存或删除过保存文件在我们的生活中,我们可能会无意间按错按钮或误读指令。容错原则旨在最大限度地减少此类事件的发生。
The tolerance of error principle states that adverse consequences of interacting with the design should be minimized. We’ve all accidentally saved over or deleted a save file at one point in our life without meaning to because we hit the wrong button or misread the directions. The tolerance of error principle is meant to minimize these types of occurrences.
精心设计的界面不一定方便或易于使用。人们喜欢快速点击或轻触浏览内容。有时,我们想让这变得更难对他们来说。为不便而设计的概念包括使界面变得不那么方便或更难交互。这似乎是一种违反直觉的设计,但如果让你删除你不小心删除的最后一个保存文件的界面不那么方便,也许你的保存文件仍然会留在我们身边。
A well-designed interface doesn’t necessarily have to be convenient or easy to use. People love to click or tap quickly through things. Sometimes, we want to make that more difficult for them. The concept of designing for inconvenience involves making interfaces less convenient or more difficult to interact with. This may seem like a counterintuitive design, but if the interface that lets you delete that last save file you accidentally deleted were less convenient, perhaps your save file would still be with us.
要求用户仔细确认删除的警告弹出窗口、切换确认按钮的位置(这样用户就不会快速点击)、在点击之间添加计时器以及要求按住,这些都是我们可以让我们的界面稍微不太方便用户交互的方式,当与界面交互的后果可能是有害的时。
Warning popups that ask users to double confirm a deletion, switching the location of affirmation buttons so that users don’t click quickly through things, adding timers between clicks, and requiring press and hold are all ways that we can make our interfaces slightly less convenient for the user to interact with when the consequences of interacting with it may be detrimental.
低体力原则规定,设计应使用舒适,并应尽量减少疲劳和不适。该原则的指导方针之一是尽量减少重复操作。这在视频游戏中似乎是不可能的,因为视频游戏往往需要大量重复操作,但以减少点击和鼠标拖动的方式组织界面可以提高UI 的质量。
The low physical effort principle states that the design should be comfortable to use and fatigue and discomfort should be minimized. One of the guidelines for this principle states to minimize repetitive actions. This may seem impossible to do in video games, which, let’s face it, tend to require a lot of repetitive actions, but organizing your interfaces in ways that reduce clicks and mouse drags can improve the quality of your UI.
减少界面操作体力消耗的一种方法是将屏幕上的类似操作分组。不要让用户在屏幕上来回切换。如果用户在单个视图菜单中完成所有操作会更方便,则不要要求他们跳转到多个菜单。您可以通过将操作分配给热键或创建快捷方式来减少鼠标使用/拖动。此外,允许用户使用箭头键或控制器按钮(而不仅仅是鼠标)浏览菜单可能会让某些用户感到更舒服。
One way to reduce physical effort with your interface is to group similar actions on the screen. Don’t make users bounce back and forth on the screen. Don’t require users to jump to multiple menus when it would be easier for them to do something all in a single view menu. You can reduce mouse usage/drag by assigning actions to hotkeys or creating shortcuts. Also, allowing users to navigate through menus with arrow keys or controller buttons rather than just the mouse may be more comfortable for some users.
在 VR 中设计 UI 时,与传统 2D 屏幕上的游戏相比,与界面交互所需的体力投入呈指数级增长。将相似的项目分组并允许用户使用控制器而不是指向来浏览菜单,将使与界面的交互更加舒适。
When designing UI in VR, the amount of physical effort needed to interact with your interface jumps exponentially compared to a game on a traditional 2D screen. Grouping similar items and allowing users to navigate menus with the controller rather than pointing will make interacting with your interface much more comfortable.
震动功能是提供界面和游戏反馈的绝佳方式,但对于某些玩家来说,震动功能可能不太舒服,据称与手臂震动综合症有关。如果您添加此功能,请务必允许用户将其关闭。
The rumble feature can be a great way to provide feedback for your interface and gameplay, but it can also be considered uncomfortable to some players and has been purportedly linked to hand-arm vibration syndrome. If you include it, be sure to allow users to turn it off.
令人沮丧的主机游戏的交互方式是将信息输入表单。如果玩家使用屏幕键盘输入长文本字符串,则必须快速导航到每个字母,这非常繁琐。考虑允许玩家在手机或电脑上输入这些长字符串,然后将数据发送到游戏,而不是在主机上。
One frustrating interaction on a console game is entering information into a form. If you have players enter long text strings with an on-screen keyboard, having to navigate to each letter gets very tedious, very fast. Consider the possibility of allowing players to enter these long strings on their phone or computer and send the data to the game, instead of on the console.
大小和空间的接近和使用原则指出界面应该允许用户无论他们的行动能力、体型、姿势或位置如何,都要与他们互动。这一原则与之前的低体力原则紧密相关,因为你希望他们不仅能够身体上与您的界面进行交互,而且操作起来也很舒服。
The size and space for approach and use principle states that interfaces should allow users to interact with them regardless of their mobility, size, posture, or position. This principle ties closely to the previous principle of low physical effort in that you want them to not only be able to physically interact with your interface but also do so comfortably.
在确定游戏的键盘布局时,请确保玩家不必以对于手小的人来说不可能或不舒服的方式伸展双手。如果控制器上有两个经常一起使用的按钮,则要确保按钮组合是可行的。例如,您不想要求他们同时按住 Xbox 控制器上的 X 按钮和 B 按钮。
When determining the keyboard layout for your game, make sure that the player does not have to stretch their hands in ways that are either impossible or uncomfortable for those with smaller hands. If you have two buttons on a controller that are frequently used together, you want to make sure that the button combination is doable. For example, you do not want to ask them to hold down both the X button and the B button on the Xbox controller at the same time.
允许多种类型的输入设备是这一原则的关键,因为它将允许人们使用为其特定尺寸和移动性设计的输入设备。例如,对于 PC 游戏,有些人可能会发现与控制器交互比与键盘交互更容易。
Allowing for multiple types of input devices is key to this principle as it will allow people to use input devices designed for their specific size and mobility. For example, with a PC game, some people may find interacting with a controller easier than a keyboard.
此外,允许调整输入灵敏度可以帮助那些有不同行动障碍的人。例如,我有手部疼痛和颤抖的问题。当我可以调整控制器和鼠标的灵敏度时,这让我更容易与游戏互动,既不影响游戏玩法,又可以减轻我这样做时所经历的痛苦。
Additionally, allowing for the sensitivity of your inputs to be adjusted can help those with different mobility issues. For example, I have problems with hand pain and tremors. When I can adjust the sensitivity on controllers and mice, this makes it easier for me to interact with the game in a way that doesn’t affect gameplay and can reduce the pain I experience doing so.
设计 VR 界面时,不要将 UI 项目放置得太高或太远离玩家的触及范围。这可能会使体型较小的玩家、坐着的玩家或行动不便的玩家无法与UI 进行交互。
When designing a VR interface, don’t put UI items too high or too far away from the player’s reach. This could make it impossible for smaller players, players sitting down, or players with mobility issues to interact with your UI.
在第 2 章中,我们讨论了拇指区域。这是屏幕上玩家在握着手机时拇指可以轻松触及的位置,也是大部分应放置可交互的 UI。我们还希望确保按钮在移动设备屏幕上尽可能大。不希望按钮太小且太靠近,以免手较大的人无法与它们交互。
In Chapter 2, we discussed the thumb zone. This is a location of the screen that can be easily reached by the player’s thumb while they’re holding their phone and where most of your interactable UI should be placed. We also want to make sure that buttons are as big as possible on mobile screens. You don’t want the buttons to be so small and so close together that those with larger hands would not be able to interact with them.
现在我们已经回顾了通用设计的各种原则,让我们看看如何专门为那些有缺陷和残疾的人进行设计。
Now that we’ve reviewed the various principles of universal design, let’s look at how we could design specifically for those with impairments and disabilities.
回想一下,通用设计涉及设计可供所有人普遍使用的 UI。另一方面,设计则涉及在设计时考虑特定的障碍和残障。在本节中,我将讨论几种非常具体的障碍和残障类型,以及如何设计 UI,以便让患有这些障碍和残障的人能够访问。其中一些示例将与我在通用设计部分讨论的示例重叠。
Recall that universal design involves designing UI that is universally usable for all people. Accessibility design, on the other hand, involves designing with specific impairments and disabilities in mind. In this section, I will discuss a few very specific types of impairments and disabilities and how you can design your UI in such a way that it is accessible to individuals with these impairments and disabilities. A few of these examples will overlap with the ones that I discussed in the universal design section.
在设计时您的界面,您应该考虑不同类型的视觉障碍和残疾,包括(但不限于)色盲、视力低下和失明。
When designing your interfaces, you should consider different types of visual impairments and disabilities, including (but not limited to) color blindness, low vision, and blindness.
重要信息绝不能仅通过颜色来传达,而应始终包含传达该信息的另一种方式。例如,假设一个游戏中的数字为负数时为红色,为正数时为绿色。该数字也可以有负号和正号符号来表示符号。如果数字代表某种变化,也许它可以在正数时向上飞,在负数时向下飞。这将确保色盲者仍然能够看到这些重要信息。
Essential information should never be conveyed by color alone and you should always include another way of conveying that information. For example, let’s say a game has a number that is red when negative and green when positive. That number could also have negative and positive symbols to indicate the sign. If the number represents some change, maybe it can fly up when positive and fly down with negative. This will make sure those who have color blindness will still be able to see these important pieces of information.
尽可能避免使用色盲人士无法辨别的颜色组合;如果无法做到,请使用其他指示物来区分物品。例如,如果您有一款使用颜色来指示可匹配的棋子的三消游戏,请在棋子上添加符号,以便更容易区分它们。
Whenever possible, you should avoid color combinations that will be indistinguishable for those who are color blind; if it’s not possible, use other indicators to make items distinct. For example, if you have a match three game that uses colors to indicate matchable pieces, also include symbols on the pieces to make them easier to distinguish.
The following websites offer very good information on designing accessible UI for color blindness:
如本章前面所述,您应该确保您的文本和其他 UI 元素具有非常高的对比度。这样,色盲和视力低下的人就可以更清楚地感知您的 UI。以下网站是检查两种颜色对比度的绝佳资源:https ://webaim.org/resources/contrastchecker/ 。
As stated earlier in this chapter, you should make sure that your text and other UI elements have very high contrast. This makes it easier for those with color blindness and those with low vision to be able to clearly perceive your UI. The following website is an excellent resource for being able to check the contrast between two colors: https://webaim.org/resources/contrastchecker/.
虽然上述网站显示了颜色是否满足网络法规要求的对比度,但该信息可以很好地转化为视频游戏。
While the preceding website shows if colors meet a contrast ratio required by web regulations, the information translates well to video games.
如果您的重要信息仅以弹出窗口的形式暂时显示在屏幕上,请确保它出现在玩家的视线范围内。将其放置在视线之外可能会让周边视力较差的人难以看到。更好的办法是,让玩家选择重要信息在屏幕上弹出的位置。
If you have important information that only appears temporarily on the screen in the form of a popup, make sure it appears in the player’s line of sight. Placing it outside of the line of sight could be difficult for those with low peripheral vision to see. Better yet, allow the player to choose where on the screen the important information will pop up.
请记住,根据通用设计原则,选择非常重要。允许玩家更改尽可能多的视觉元素的设置。如果您的游戏使用十字准线或光标,请允许玩家更改十字准线或光标的外观。允许玩家更改界面或文本的大小。允许玩家将界面的字体更改为字距或间距更大或花哨的字体。
Remember, according to the principles of universal design, choice is very important. Allow players to change settings for as many of the visual elements as possible. If your game uses a crosshair or cursor, allow the player to change the appearance of the crosshair or cursor. Allow your players to change the size of the interface or text. Allow players to change the font of the interface to one that has higher kerning or spacing or is less frilly.
以非纯视觉的方式显示信息也很重要。例如,假设您的 UI 变成红色,表示玩家生命值较低,屏幕上显示红色生命值计。您还可以添加蜂鸣声和控制器振动来指示生命值较低。这将有助于视力低下和色盲的人能够感知生命值较低状态。
It’s also important to indicate information in ways that are not purely visible. For example, suppose your UI turns red to indicate your player has low health and you have a red health meter on the screen. You can also include a beeping sound and a vibration of the controller to indicate low health. This will help those with both low vision and color blindness be able to perceive the low health status.
如果您有输入框,您可以使用语音作为输入文本的方式,而不仅仅是使用视觉键盘进行输入。
If you have input boxes, you can use speech as a way to input text, rather than just a visual keyboard for input.
请记住,你可以还可以使用描述轨道来描述游戏的所有用户界面元素,从而让视力低下和失明的人仍然能够感知屏幕上的内容。
Remember, you can also use a description track to describe all of the user interface elements of your game, thus allowing those with low vision and blindness to still be able to perceive what is on the screen.
有多种方法可以让听力障碍人士访问你的界面,残障人士的言语障碍。本质上,您不希望任何重要信息仅通过声音传达,也不希望任何输入都需要语音。此外,如果您的 UI 产生任何类型的噪音,请将重叠噪音降至最低。
There are multiple ways in which you can make your interface accessible to those with hearing and speech impairments in disabilities. Essentially, you do not want any important information to be conveyed through sound alone and you do not want to require speech for any of your inputs. Additionally, if your UI makes any type of noise, keep overlapping noises to a minimum.
在我之前描述的低生命值示例中,除了哔哔声之外,还有多种方式可以让玩家意识到低生命值。如果您的 UI 包含语音输入,请确保它不是唯一的输入形式,并允许用户通过其他方式输入信息。
In the low-health example I described previously, there were multiple ways other than just the beeping sound in which the player was made aware of the low health. If your UI includes speech input, make sure that it is not the only form of input and allows users to input information through other means.
虽然看起来界面设计(包括所有对话的字幕)不需要设计为 UI 元素,但字幕的 UI 需要清晰易读。通常,您应该将它们放在某种框中,以便它们与背景形成鲜明对比。您还需要确保它们与游戏中的口语同步。
While it might not seem like an interface design, including subtitles for all spoken dialogue, would need to be designed as a UI element. Your UI for subtitles will need to be clear and readable. Usually, you should put them in some sort of box so that they easily contrast against the background. You also want to make sure that they sync up with the spoken speech in the game.
在设计界面时,您需要确保它们适合各种行动能力水平的用户。让您的游戏适合各种行动能力水平的用户的最佳方式是包含为输入设备、输入映射和配置提供尽可能多的选项。允许玩家自行选择如何与界面交互可以确保界面满足他们特定的移动需求。
When designing your interfaces, you want to make sure they are accessible for people with all levels of mobility. The best way to make your game accessible to mobility levels is to include as many options as possible for input devices, input mappings, and configurations. Allowing the player to choose for themselves how they interact with your interface might ensure that it fits their particular mobility needs.
您希望您的控件尽可能简单,并且需要的按钮尽可能少。如果您的控件特别复杂,请提供简化控制方案的替代方案。
You want your controls to be as simple as possible and require as few buttons as possible. If your controls are particularly complicated, provide alternatives that simplify the control scheme.
此外,不要要求玩家在游戏中使用与游戏其他部分不同的输入方式。例如,如果游戏的大部分内容都是通过键盘控制的,则不要要求玩家使用鼠标与开始屏幕进行交互。允许玩家使用游戏的各个方面均使用相同的输入设备。
Additionally, don’t require the player to use an input for a small part of the game that is different than the rest of the game. For example, if most of the game is controlled by a keyboard, don’t require the start screen to be interacted with by a mouse. Allow the player to use the same input device for all aspects of the game.
在设计用户界面时,考虑玩家的认知和情感状态以及他们的语言技能非常重要。
It’s important to consider the cognitive and emotional states of your players as well as their language skills when designing your user interface.
不要假设确保玩家能够阅读您创建 UI 时使用的语言,或者能够快速阅读。如果您的游戏包含文本,请使用其他上下文元素和图像来传达文本的意图。如果您的 UI 中显示了一些文本,然后又消失了,请允许玩家选择文本消失的时间,而不是让文本按照自己的速度消失。
Don’t assume that your player can read the language in which you have created your UI or that they can read quickly. If your game contains text, use other contextual elements and images to convey the text’s intent. If you have text that displays in your UI and then goes away, allow players the ability to choose when it goes away rather than having it do so at its own speed.
使用 UI 提醒玩家重要的游戏元素和有关如何与界面交互的重要信息。通过在 UI 中突出显示控件来提醒玩家控件是什么。使用 UI 清楚地指示交互元素和目标。
Use your UI to remind players of important gameplay elements and important information about how to interact with your interface. Remind your players of what the controls are by prominently displaying them in your UI. Use your UI to clearly indicate interactive elements and objectives.
不要在用户界面中放置闪烁的图像或重复的图案,因为这可能会引发易患癫痫的人的症状。
Don’t place flickering images or repetitive patterns in your UI as this could be triggering to individuals prone to seizures.
虽然这可能与你的游戏玩法而非用户界面更相关,但我想指出的是,在具有特别触发内容的游戏中设置内容警告有助于让用户为可能引起困扰的事情做好准备。例如,现在许多游戏如果包含令人不安的内容(例如 Doki Doki Literature Club),都会附带内容警告。
While this is probably more related to your gameplay than your user interface, I would like to point out that content warnings in your games with particularly triggering content can be helpful to prepare your user for things that may cause distress. For example, many games now come with a content warning if they have upsetting content (such as Doki Doki Literature Club).
关于用户界面的通用设计和可访问性设计方面,我还有很多话要说,但遗憾的是,这不是一本关于 UI 设计的书,而是关于 UI 开发的书。我希望本章至少能让您了解考虑这些元素的重要性,以及在用户界面开发之初考虑它们的重要性。
There is so much more I could say about the universal design and accessibility design aspects of user interfaces, but alas, this is not a book about designing UI it is about the development of UI. I hope this chapter at least gave you an insight into how important it is to consider these elements and how important it is to consider them at the beginning of your user interface development.
如果你想了解更多关于无障碍设计的知识,我强烈建议你查看以下网站提供的信息:https ://gameaccessibilityguidelines.com/full-list/ 。它提供了与视频游戏可访问性相关的最佳实践的详尽列表。此外,它还提供了多个最佳实践示例供您参考。
If you’d like to learn more about accessibility design, I highly recommend that you review the information provided at the following website: https://gameaccessibilityguidelines.com/full-list/. It provides an extensive list of best practices related to accessibility for video games. Additionally, it provides multiple examples of best practices that you can use for reference.
您还可以访问https://caniplaythat.com/,该网站提供有关游戏行业可访问性的信息。
You can also visit https://caniplaythat.com/, a website that provides information about accessibility in the gaming industry.
在本章中,我们讨论了设计 UI 时的一些关键考虑因素,以便让其尽可能地被普遍感知。我们讨论了通用设计的原则,并回顾了一些可以在设计中实施的考虑因素,以提高其可访问性。
In this chapter, we discussed some key considerations for designing your UI so that it will be as universally perceivable as possible. We discussed the principles of universal design and reviewed some considerations you can implement in your designs to improve their accessibility.
在下一章中,我们将抛开设计讨论,开始研究如何实现 UI。我们将回顾 Unity 中可用的不同界面系统,以便您可以开始运用这些设计知识!
In the next chapter, we will leave the design discussion behind and start looking at how to implement UI. We’ll review the different interface systems available in Unity so that you can start putting some of this design knowledge to use!
现在我们已经讨论了开发用户界面的各种设计注意事项,我们可以开始讨论如何在 Unity 中实现它们。Unity 提供了各种用于创建 UI 的系统。它有现成的系统允许您创建将在您的游戏中显示的 UI,或仅在编辑器中显示的 UI。此外,它还提供了多个用于接收来自玩家的输入的系统。
Now that we’ve discussed various design considerations for developing user interfaces, we can start discussing how to implement them within Unity. Unity provides various systems for creating UI. It has systems in place that allow you to create a UI that will be displayed in your game, or UI that will be displayed only in the Editor. Additionally, it provides multiple systems for receiving input from the player.
在撰写本文时,其中两个系统仍在积极开发中,默认情况下未包含在 Unity 中。本书将主要关注已完成系统的开发,但由于 Unity 确实打算在某个时候将这些系统作为标准功能,因此,如果因为它们仍处于预览阶段而不讨论它们,那我就太失职了。
At the time of writing, two of these systems are still in active development and do not come packaged in Unity by default. This book will primarily focus on development with the systems that are complete, but since Unity does intend to make these systems standard features at some point, I would be remiss not to discuss them just because they are still in preview.
在本章中,我将讨论以下主题:
In this chapter, I will discuss the following topics:
让我们首先了解一下 Unity 中的三个 UI 系统。
Let’s start by looking at the three UI systems within Unity.
Unity 有三个系统可以用于构建 UI。您选择哪种方式取决于您的 UI 将在何处显示、您要完成的任务、您是否正在处理现有项目以及您对编码的熟悉程度。
Unity has three systems that can be used to build UI. Which you choose will depend on where your UI will be displayed, what you are trying to accomplish, whether you’re working on a pre-existing project, and how comfortable you are with coding.
您构建的 UI 可以是游戏内的,也可以是编辑器内的。游戏内的 UI 是玩家可以访问的 UI。编辑器内的 UI 是在 Unity 编辑器内显示的并有助于开发的 UI。
The UI you build can either be in-game or in-Editor. In-game UI is the UI that can be accessed by your players. In-Editor UI is UI that displays within the Unity Editor and assists with development.
如果您想为游戏或应用程序构建 UI,您可以选择 Unity UI (uGUI) 系统或 UI 工具包。如果您想构建在 Unity 编辑器中显示的 UI,您可以使用 UI工具包或 IMGUI。以下维恩图总结了不同 UI 系统的用途:
If you want to build UI for your game or application, you can choose between the Unity UI (uGUI) system or the UI Toolkit. If you want to build UI that appears in your Unity Editor, you can use either the UI Toolkit or IMGUI. The following Venn diagram summarizes the uses of the different UI systems:
图 5.1:游戏内和编辑器内 UI 的比较
Figure 5.1: A comparison of in-game and in-Editor UI
看看图 5.1,您可能会想,“好吧,UI Toolkit 适用于一切!我只需学习它,就可以完成我的 UI 学习之旅!这是一个简单的选择!”不幸的是,事情并没有那么简单。让我们更深入地了解不同的系统,以便您可以决定哪个适合您。
Looking at Figure 5.1, you may be thinking, “Well, UI Toolkit works for everything! I’m just going to learn that and be done with my UI learning journey! That was an easy choice!” Well, unfortunately, it’s not that simple. Let’s look at the different systems a little more in-depth so that you can decide which is right for you.
Unity UI系统(也称为uGUI )是 Unity内置的开箱即用系统,无需任何额外下载。它基于 GameObject 和 Component,包含多种类型的 UI可供选择的元素。当谈到为游戏或应用程序开发 UI 时,这是最强大和最稳定的选择。由于这是 Unity 中唯一一个非预览模式的游戏内 UI 构建系统,因此本文的大部分内容将重点介绍如何使用该系统开发 UI。
The Unity UI system, also known as uGUI, is the system that is built into Unity out of the box and doesn’t require any additional downloads. It is GameObject and Component-based and includes multiple types of UI elements to choose from. When it comes to developing UI for a game or application, this is the most robust and stable option. Since this is the only system for building in-game UI that is not in preview mode and is included within Unity, the majority of this text will focus on how to develop UI using this system.
IMGUI (即时模式 GUI )系统是一种基于代码的 GUI 系统,用于在编辑器内创建界面。其主要功能是协助程序员进行开发,不建议用于由于性能问题,游戏内 UI 的开发暂停。由于此系统并非旨在在游戏中使用,并且本书主要关注游戏的 UI 开发,我不会花大量时间来介绍它,但我将在第 19 章讨论它的一些基本功能和用法。
The IMGUI, or Immediate Mode GUI, system is a code-based GUI system used to make interfaces within the editor. Its primary function is to assist programmers with development, and it is not recommended for the development of in-game UI due to performance issues. Due to the fact that this system is not intended to be used in-game and this book will primarily concern itself with UI development for games, I won’t spend a significant amount of time covering it, but I will discuss some of its basic functionality and usage in Chapter 19.
UI Toolkit是正在积极开发的新 UI 系统。默认情况下,它不包含在引擎中,必须与包管理器。此外,预览包,这意味着您必须选择在 Unity 提供的可用包列表中查看它。Unity 确实计划最终用 UI Toolkit 替换 uGUI 和 IMGUI。UI Toolkit 采用传统的 Web 开发概念设计,其结构与基于 GameObject 的 uGUI 完全不同。在第 18 章中,我将介绍如何下载 UI Toolkit 包以及如何使用它。
UI Toolkit is a new UI system that is in active development. It is not included within the engine by default and must be downloaded with the Package Manager. Additionally, it is a preview package, which means you have to elect to even see it in the list of available packages provided by Unity. Unity does plan on replacing both uGUI and IMGUI with UI Toolkit eventually. UI Toolkit is being designed with traditional web-development concepts and is structured entirely differently than the GameObject-based uGUI. In Chapter 18, I will cover how to download the UI Toolkit package and how to work with it.
您选择哪种系统使用将取决于一些因素。如前所述,您是为编辑器还是游戏制作 UI 将决定您选择哪个系统。如果您正在为游戏制作 UI,则可以使用 Unity UI 或 UI Toolkit。如果您正在为编辑器制作 UI,则可以使用 IMGUI 或UI Toolkit。
Which system you choose to use is going to depend on a few things. As discussed earlier, whether you are making UI for the Editor or a game will determine which system you choose. If you’re working on UI for a game, you can use Unity UI or UI Toolkit. If you’re working on UI for the Editor, you can use IMGUI or UI Toolkit.
UI Toolkit 是一个尚未完全实现的新系统,因此如果你正在开发一款已有 UI 的游戏,你可能不会使用 UI Toolkit。它也可能不具备所有功能您希望与之合作。由于 UI Toolkit 正在开发中,因此不能保证其稳定性,并且在开发过程中对其进行更新可能会对您的项目产生不利影响。
UI Toolkit is a new system that is not fully implemented, so if you are working on a pre-existing game with UI already in place, you probably won’t be using UI Toolkit. It also may not have all the features you are looking to work with. Because UI Toolkit is in development, it is not guaranteed to be stable, and updating it mid-development may adversely affect your project.
您对编码的熟悉程度也会影响您的决定。一般来说,使用 Unity UI 所需的编码工作量较少比 IMGUI 更简单,可能比 UI Toolkit 更熟悉,因为它是基于 GameObject 的。但是,如果您熟悉基于 Web 的 UI 创建,那么 UI Toolkit对您来说可能非常熟悉。
Your comfort with coding could also drive your decision. In general, the coding required to use Unity UI is less intensive than IMGUI and may be more familiar to you than UI Toolkit, since it is GameObject-based. However, if you are familiar with web-based UI creation, UI Toolkit may seem really familiar to you.
如果您正在考虑使用 UI 工具包,我建议您在决定使用哪个系统之前查看本书中的示例以及以下 Unity 文档页面:https://docs.unity3d.com/Manual/UI-system-compare.xhtml。
If you are considering using the UI Toolkit, I recommend reviewing the examples in this book, as well as the following Unity documentation page, before you make your decision on which system to use: https://docs.unity3d.com/Manual/UI-system-compare.xhtml.
现在我们已经回顾了 Unity 提供的三个 UI 系统,我们可以回顾一下两个输入系统。
Now that we’ve reviewed the three UI systems provided by Unity, we can review the two input systems.
根据第 1 章的定义,UI代表用户界面,涵盖用户和游戏相互传达信息。在讨论三个 UI 时系统,我们讨论了游戏与用户沟通的三种方式——具体来说,是通过在输出设备(即屏幕)上使用 GUI。但是,如果用户想要与游戏沟通,用户必须有一些可以提供输入的手段。然后游戏需要处理该输入。
As defined in Chapter 1, UI stands for user interface and encompasses all mechanisms by which the user and the game convey information to each other. When discussing the three UI systems, we talked about three ways in which the game communicates with the user – specifically through the use of GUI on an output device (i.e., a screen). However, if the user wants to communicate with the game, the user will have to have some means through which they can provide input. The game will then need to process that input.
Unity 有两种核心方式可以处理输入。输入管理器或输入系统。就像除了各种因素会决定您使用哪种 UI 系统之外,还有各种因素可以帮助您确定要使用哪种输入系统。这两种系统都允许您轻松地处理多种类型的输入,就好像它们是同一件事一样。例如,每个系统都允许您处理键盘空格键和 Xbox 控制器 A 按钮,就好像它们是同一种类型的输入一样。它们如何做到这一点将在后面的章节中更详细地讨论,但现在,我们只看一下两者之间的一般差异。
There are two core ways in which Unity can handle input. The Input Manager or the Input System. Just as there are varying factors that would determine which UI system you may use, there are also varying factors that can help you determine which input system to use. Both systems allow you to easily process multiple types of input as if they are the same thing. For example, each system will let you process a keyboard space bar and an Xbox controller A button as if they are the same type of input. How they do that will be discussed more thoroughly in later chapters, but for now, let’s just look at the general differences between the two.
输入管理器是输入系统,Unity 默认自带,无需下载任何额外软件包。无需配置任何设置,您可以轻松接受来自键盘、鼠标、操纵杆或触摸屏等。它通过提供预定义的输入轴来实现这一点,这些输入轴本质上指定了绑定到它们的关键字和按钮。我们将在第 8 章中更详细地回顾它是如何运作的。
The Input Manager is the input system that comes with Unity by default and does not require any additional package downloads. Without having to configure any settings, you can easily accept input from things such as a keyboard, mouse, joystick, or touchscreen. It achieves this by providing pre-defined input axes that essentially specify keywords and buttons that bind to them. We will review how this functions more thoroughly in Chapter 8.
输入系统(俗称新输入系统)是一个目前正在开发的软件包,正如 Unity其文档中指出,旨在比输入管理器更强大、更灵活、更可配置:https ://docs.unity3d.com/Packages/com.unity.inputsystem@1.3/manual/index.xhtml 。
The Input System (colloquially referred to as the new Input System) is a package that is currently in development and is, as Unity states in its documentation, intended to be more powerful, flexible, and configurable than the Input Manager: https://docs.unity3d.com/Packages/com.unity.inputsystem@1.3/manual/index.xhtml.
如果你一直在如果您的项目正在使用输入管理器,则可以将您的项目转换为使用新输入系统的项目。我们将在第 20 章中讨论如何实现输入系统。
If you have been working on a project that is using the Input Manager, it is possible to convert your project to one that uses the new Input System. We will discuss how to implement the Input System in Chapter 20.
首先我要说的是,选择哪种系统不一定有正确答案。理论上,你可以用任何一个系统处理你想要的任何类型的输入。你选择哪个系统主要取决于你的偏好、你的项目输入集的复杂程度,以及您正在为多个平台进行开发。
Let me start by saying there’s not necessarily a right answer on which system you choose. You can theoretically process any type of input you wish with either system. Which you choose will primarily be based on preference, how complicated your project’s set of inputs is, and whether you are developing for multiple platforms.
如果您不打算让播放器重新映射控件(如第 4 章所述)或不打算进行跨平台开发,那么使用输入管理器可能没问题,无需下载输入系统。但是,如果您想拥有超可配置的控制方案、接受来自各种设备的输入并接受复杂的输入操作,那么您可能会发现使用输入系统编写处理这些输入的代码比使用输入管理器更容易。
If you are not planning on allowing your player to remap controls (as discussed in Chapter 4) or are not planning on cross-platform development, you are probably fine with using the Input Manager and don’t need to go through the process of downloading the Input System. However, if you want to have ultra-configurable control schemes, accept inputs from a variety of devices, and accept complicated input actions, you will likely find it easier to write the code that processes these inputs using the Input System than you would using the Input Manager.
由于输入管理器用于目前正在开发的许多项目,因此,如果我仅仅因为它不是新的热门而完全忽略它,那对您来说就太不公平了。此外,新的输入系统仍然很新,仍在积极开发中,因此可能会发生重大变化每次更新都会出现这种情况。但是,它确实使某些东西的构建变得更容易,并且在开发人员中越来越受欢迎。因此,我不会只选择其中一个系统来在本书中重点介绍。
Since the Input Manager is used in so many projects that are currently in development, I would be doing you a disservice to completely omit it just because it is not the new hot. Additionally, the new Input System is still new and still in active development, so it is subject to drastic changes with each update. However, it does make some things significantly easier to build and is gaining traction in popularity among developers. Therefore, I will not choose just one of these systems to focus on in this book.
Unity 提供了多种方式,您可以通过三种 UI 系统向用户显示信息。选择哪种方式取决于您的需求以及您是为游戏还是编辑器开发 UI。本书将主要关注 uGUI,因为它是用于游戏内开发的最稳定的 UI 版本,并且已在 Unity 中提供,无需额外下载。但是,如何使用 IMGUI 开发编辑器 UI 以及如何使用 UI 工具包来同时使用编辑器 UI 和游戏内 UI 将在本书的后面章节中讨论。
Unity provides multiple ways in which you can display information to your user through the use of three UI systems. Which you choose depends on your needs and whether you are developing UI for a game or the Editor. This book will primarily focus on uGUI, since it is the most stable UI version used for in-game development and is provided within Unity, without additional downloads. However, how you can use IMGUI to develop Editor UI and the UI Toolkit to use both Editor UI and in-game UI will be discussed in the later chapters of this book.
Unity 还提供了多种处理用户输入的方法。虽然新的输入系统仍在开发中,并且不是 Unity 的默认功能,但我将确保为您提供足够的信息,以便在您的项目中使用它。
Unity also provides multiple ways in which you can process inputs from a user. While the new Input System is still in development and does not come with Unity by default, I will make sure to give you enough information to use it in your projects.
在下一章中,我们将开始使用 uGUI 系统开发 UI,通过探索 UI 画布、面板和布局。
In the next chapter, we will start developing UI using the uGUI system, by exploring UI Canvases, Panels, and layouts.
在本部分中,您将学习使用Unity UI ( uGUI ) 系统的基础知识。您将学习如何使用 Canvases 和 Panels 及其 Rect Transform 组件来布局 UI。您还将了解如何使用 Unity 的各种自动布局组件来帮助您更轻松地设计 UI。最后,您将了解如何为 uGUI 系统编写代码以及如何为您的 UI 编写交互程序。
In this part, you will learn the basics of working with the Unity UI (uGUI) system. You’ll learn how to lay out UIs with the use of Canvases and Panels and their Rect Transform component. You’ll also look at how you can use Unity’s various Automatic Layout components to help you design UI more easily. Lastly, you’ll look at how you can write code for the uGUI System and program interactions for your UI.
本部分包含以下章节:
This part has the following chapters:
如上一章所述,本文的大部分内容将重点介绍 Unity UI 系统 uGUI。画布是使用 Unity UI 制作的所有 UI 的核心。每个 uGUI 元素都必须包含在画布中,才能在场景中渲染。它的工作原理类似于艺术家绘画的画布,但我们不是在画布上绘画,而是在画布上布置 UI 元素。因此,我们将从画布开始探索 uGUI 系统中提供的各种 UI 元素。
As discussed in the previous chapter, the majority of this text will focus on the Unity UI system, uGUI. Canvases are the core of all UI made with Unity UI. Every single uGUI element must be contained within a Canvas for it to render within a scene. It works similarly to a canvas on which an artist paints, but instead of painting on them, we lay out UI elements on them. So, we’ll start our exploration of the various UI elements provided in the uGUI system with Canvases.
画布的作用不仅是容纳所有 UI 元素,还决定元素的渲染方式和缩放方式。尽早设置可在多种分辨率和宽高比下缩放的 UI 非常重要,因为以后再尝试这样做会带来很多麻烦和额外的工作。因此,我们还将讨论如何确保我们的 UI 能够适当缩放。
Canvases serve the purpose of not only holding all the UI elements within them but also determining how the elements will render and how they will scale. It’s important to start focusing on setting up a UI that will scale at multiple resolutions and aspect ratios early on, as trying to do so later will cause a lot of headaches and extra work. Therefore, we will also discuss how to make sure our UI scales appropriately.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
您可以在这里找到本章的相关代码和资产文件:
You can find the relevant codes and asset files of this chapter here:
每一个 UI 元素create 必须是UI Canvas的子项。要查看可以在 Unity 中创建的所有 UI 元素的列表,请从Hierarchy窗口中选择+ | UI,如以下屏幕截图所示:
Every UI element you create must be a child of a UI Canvas. To see a list of all UI elements you can create within Unity, select + | UI from the Hierarchy window, as shown in the following screenshot:
图 6.1:Unity UI(uGUI)系统中可渲染的 UI 元素
Figure 6.1: The renderable UI elements within the Unity UI (uGUI) system
上图突出显示的每个 UI 项目都是可渲染的 UI 项目,必须包含在 Canvas 中才能渲染。如果您尝试将任何这些 UI 元素添加到不包含 Canvas 的场景中,则 Canvas 将自动添加到场景中,并且您尝试创建的项目将成为新添加的 Canvas 的子项。为了演示这一点,请尝试向空场景添加新的UI 文本元素。您可以通过选择+ | UI | Text来执行此操作。
Every one of the UI items highlighted in the preceding screenshot is a renderable UI item and must be contained within a Canvas to render. If you try to add any of those UI elements to a scene that does not contain a Canvas, a Canvas will automatically be added to the scene, and the item you attempted to create will be made a child of the newly added Canvas. To demonstrate this, try adding a new UI Text element to an empty scene. You can do so by selecting + | UI | Text.
这将导致三个新项目出现在层次结构列表中:Canvas、Text和EventSystem,其中 Text 是Canvas 的子项。
This will cause three new items to appear in the Hierarchy list: Canvas, Text, and EventSystem, where the Text is a child of the Canvas.
图 6.2:向场景中添加 UI 文本元素的结果
Figure 6.2: The result of adding a UI Text element to the scene
现在您的场景中已经有了一个 Canvas,您添加到场景中的任何新 UI 元素都将自动添加到此Canvas。
Now that you have a Canvas in your scene, any new UI elements you add to the scene will automatically be added to this Canvas.
笔记
Note
如果您尝试从 Canvas 中取出可渲染的 UI 元素,它将不会被绘制到场景中。
If you try to take a renderable UI element out of a Canvas, it will not be drawn to the scene.
您还可以通过选择+ | UI | Canvas来创建一个空的 Canvas 。当您在场景中创建新的 Canvas 时,如果场景中尚不存在EventSystem GameObject,则会自动为您创建一个(如您在前面的屏幕截图中看到的那样)。我们将在第 8 章中进一步讨论EventSystem GameObject ,但现在,您真正需要知道的是EventSystem允许您与 UI 项目进行交互。
You can also create an empty Canvas by selecting + | UI | Canvas. When you create a new Canvas in a scene, if an EventSystem GameObject does not already exist within the scene, one will automatically be created for you (as you saw in the preceding screenshot). We’ll discuss the EventSystem GameObject further in Chapter 8, but for now, all you really need to know is the EventSystem allows you to interact with the UI items.
笔记
Note
场景中可以有多个 Canvas,每个 Canvas 都有自己的子项。
You can have more than one Canvas in your scene, each with its own children.
当你创建一个 Canvas 时,它会在你的场景中显示为一个大矩形。它会比那个大得多代表相机视图的矩形:
When you create a Canvas, it will appear as a large rectangle within your scene. It will be significantly larger than that rectangle representing the camera’s view:
图 6.3:Unity UI(uGUI)系统中可渲染的 UI 元素
Figure 6.3: The renderable UI elements within the Unity UI (uGUI) system
Canvas 比 Camera 大,因为 Canvas 组件具有缩放模式。默认情况下,缩放模式相当于 UI 内的一个像素到一个 Unity 单位,因此它要大得多。这种大尺寸的一个好处是,您可以很容易地将 UI 项目视为一个独立的实体,这样就不会使相机视图变得混乱。
The Canvas is larger than the Camera because the Canvas component has a scaling mode on it. The scaling mode by default equates to one pixel within the UI to one Unity unit, so it’s a lot bigger. A nice consequence of this large size is that it is really easy to see your UI items as a somewhat separate entity, and this keeps it from cluttering up your camera view.
每个新创建的 Canvas 都会自动附带四个组件:Rect Transform、Canvas、Canvas Scaler和Graphic Raycaster,如以下屏幕截图所示:
Every newly created Canvas automatically comes with four components: Rect Transform, Canvas, Canvas Scaler, and Graphic Raycaster, as shown in the following screenshot:
图 6.4:Canvas 游戏对象的组件
Figure 6.4: The components of a Canvas GameObject
Let’s explore each of these components.
每个 Unity UI 游戏对象都有一个Rect Transform组件作为其第一个组成部分。这组件与非 UI 游戏对象上的Transform组件非常相似,因为它允许您将对象放置在场景中。
Every Unity UI GameObject has a Rect Transform component as its first component. This component is very similar to the Transform component on non-UI GameObjects in that it allows you to place the object within the scene.
您会注意到,当您第一次将 Canvas 放置在场景中时,您无法调整Rect Transform中的值,并且会显示一条消息,指出“某些值由 Canvas 驱动”,如上图所示。此消息意味着您无法控制 Canvas 的位置,因为Canvas组件中选择的属性决定了它们。
You’ll note that when you first place a Canvas in the scene, you can’t adjust the values within the Rect Transform, and there is a message stating, “Some values driven by Canvas”, as shown in the preceding screenshot. This message means you cannot control the position of the Canvas because the properties selected in the Canvas component determine them.
当 Canvas 组件具有将其渲染模式设置为屏幕空间-叠加或屏幕空间-相机时,Rect Transform组件的值的调整将被禁用。在这两种模式下,值由游戏显示的分辨率决定,因为 Canvas 填满了全屏区域。当 Canvas 的渲染模式设置为世界空间时,您可以根据需要调整值,因为此组件将确定其在场景中的位置。我们将在本文后面更详细地讨论如何使用Rect Transform组件章,但现在,让我们更彻底地回顾一下 Canvas 组件和各种渲染模式。
When a Canvas component has its Render Mode set to Screen Space-Overlay or Screen Space-Camera, the adjustment of the Rect Transform component’s values is disabled. In these two modes, the values are determined by the resolution of the game display because the Canvas fills up the full-screen area. When the Canvas’ Render Mode is set to World Space, you can adjust the values as you see fit, as this component will then determine its location within the scene. We will discuss how to use the Rect Transform Component more thoroughly later in this chapter, but for now, let’s review the Canvas component and the various render modes more thoroughly.
Canvas组件允许您可以从下拉菜单中选择 Canvas渲染模式。有三种渲染模式:屏幕空间-叠加、屏幕空间-相机和世界空间。不同的渲染模式决定了 UI 元素在场景中的绘制位置,以及如何使用Rect Transform组件。
The Canvas component allows you to select the Canvas Render Mode from a dropdown. There are three render modes: Screen Space-Overlay, Screen Space-Camera, and World Space. The different render modes determine where in the scene the UI elements will be drawn and how the Rect Transform component will be used.
在开发 UI 时,您应该始终做的第一件事就是根据需要适当地设置 Canvas 的渲染模式。如果场景中有多个 Canvas,则每个 Canvas 可以具有不同的渲染模式。更改渲染模式将更改 Canvas 组件上可用的属性。让我们回顾一下每种渲染模式的用途及其附带的属性。
When developing your UI, the first thing you should always do is appropriately set your Canvas’ render mode based on your needs. If you have multiple Canvases in your scene, they can each have a different render mode. Changing the render mode will change the properties available on the Canvas component. Let’s review the purpose of each of the render modes and the properties that come with them.
屏幕空间覆盖是默认渲染模式。如果让你想象一个视频游戏的 UI,你很可能会想象一个在屏幕空间覆盖中渲染的UI。此渲染模式将 Canvas 中的所有 UI 元素覆盖在场景中的所有内容前面,就像在屏幕顶部绘制一样。因此,UI 项目像平视显示器(HUD)和与屏幕出现在同一平面上的弹出窗口将包含在屏幕空间覆盖画布内。
Screen Space-Overlay is the default render mode. If you are asked to think of a video game UI, it’s highly likely that you will envision one rendered in Screen Space-Overlay. This render mode overlays all the UI elements within the Canvas in front of everything in the scene as if it is drawn on top of the screen. So, UI items like heads-up-displays (HUDs) and pop-up windows that appear on the same plane as the screen will be contained within a Screen Space-Overlay Canvas.
请记住,当 Canvas 使用屏幕空间叠加渲染模式时,您无法调整Canvas 的Rect Transform组件。这是因为画布将根据以下情况自动调整大小:屏幕(不是相机)的尺寸。
Remember, when a Canvas is using the Screen Space-Overlay render mode, you cannot adjust the Rect Transform component of the Canvas. This is because the canvas will automatically resize based on the size of the screen (not the camera).
图 6.5:屏幕空间 – 叠加渲染模式属性
Figure 6.5: Screen Space – Overlay Render Mode properties
当您选择“屏幕空间叠加”时,以下属性将可用:
When you have Screen Space-Overlay selected, the following properties become available:
接下来我们看一下屏幕空间相机。
Next, let’s look at Screen Space Camera.
屏幕空间-相机的性能与屏幕空间-叠加类似,但它将所有 UI 元素渲染为距离相机特定距离。从以下屏幕截图中可以看到,如果没有选择渲染相机,则此渲染模式的工作方式与屏幕空间叠加模式完全相同(如警告消息所示):
Screen Space-Camera performs similarly to Screen Space-Overlay, but it renders all UI elements as if they are a specific distance away from the camera. As you can see from the following screenshot, if there is no Render Camera selected, this render mode works exactly like the Screen Space-Overlay mode (as indicated by the warning message):
图 6.6:屏幕空间的警告消息 - 相机渲染模式
Figure 6.6: Warning message for Screen Space – Camera Render Mode
您可以在屏幕空间相机渲染模式中将场景中的任何相机分配给渲染相机。这是画布将绘制到的相机。将相机添加到渲染相机插槽后,警告消息将消失,并且将为您提供新的选项,如以下屏幕截图所示:
You can assign any camera in your scene to the Render Camera in the Screen Space-Camera render mode. This is the camera to which the canvas will draw. Once you add a camera to the Render Camera slot, the warning message will disappear, and new options will be made available to you, as shown in the following screenshot:
图 6.7:屏幕空间 – 相机渲染模式属性
Figure 6.7: Screen Space – Camera Render Mode properties
When you have Screen Space-Camera selected, the following properties become available:
如果您希望 Canvas 从与主摄像头不同的角度进行渲染,此渲染模式非常有用。在 2D 游戏中,创建与摄像头一致缩放的静态背景也很有用。由于您可以在此渲染模式下使用Sprite Sorting Layer,因此您可以确保包含背景的 Canvas 始终在场景中所有其他对象的后面进行渲染。
This rendering mode is helpful if you want a Canvas to render from a different perspective than that of your main camera. It is also helpful for creating a static background that will consistently scale with the camera in a 2D game. Since you can use Sprite Sorting Layer with this rendering mode, you can make sure the Canvas containing the background always renders behind all other objects in the scene.
请记住,当 Canvas 是使用屏幕空间-相机渲染模式时,您无法调整画布的Rect Transform组件。这是因为画布会根据相机(而不是屏幕)的大小自动调整大小。
Remember, when a Canvas is using the Screen Space-Camera render mode, you cannot adjust the Rect Transform component of the Canvas. This is because the canvas will automatically resize based on the size of the camera (not the screen).
最后一种渲染模式是World Space。此模式允许您渲染 UI 元素,就好像它们物理上位于世界。
The last rendering mode is World Space. This mode allows you to render UI elements as if they are physically positioned within the world.
在屏幕空间叠加和屏幕空间相机中,您无法调整Rect Transform组件的属性。具有这两种渲染模式的 Canvas 中的 UI 元素的位置不会转换为世界空间坐标,而是相对于屏幕和相机。但是,当 Canvas 处于世界空间渲染模式时,可以调整Rect Transform的值,因为 UI 元素的坐标基于场景内的实际位置。这些 Canvas 不必像其他两种 Canvas类型那样面向指定的相机。
In Screen Space-Overlay and Screen Space-Camera, you cannot adjust the properties of the Rect Transform component. The positions of UI elements within Canvases with those two rendering modes do not translate to world space coordinates and are instead relative to the screen and camera. However, when a Canvas is in World Space render mode, the values of the Rect Transform can be adjusted because the coordinates of the UI elements are based on actual positions within the scene. These Canvases do not have to face a specified camera as the other two Canvas types do.
图 6.8:渲染模式 – 世界空间属性
Figure 6.8: Render Mode – World Space properties
此模式请求事件相机,而不是像屏幕空间相机模式那样请求渲染相机。事件相机不同于渲染相机。由于此画布位于世界空间中,它将使用主相机进行渲染,就像场景中存在的所有其他对象一样。事件相机是将从事件系统接收事件的相机。因此,如果此画布上的项目需要交互,则必须包含事件相机。如果玩家不会与画布上的项目交互,则可以将其留空。
This mode requests an Event Camera, rather than a Rendering Camera as Screen Space-Camera mode requested. An Event Camera is different than a Rendering Camera. Since this Canvas is in the World Space, it will be rendered with the Main Camera, just as all the other objects that exist within the scene. The Event Camera is the camera that will receive events from the EventSystem. So, if items on this Canvas require interactions, you have to include an Event Camera. If the player won’t be interacting with the items on the Canvas, you can leave this blank.
您需要指定事件相机的原因是光线投射。当用户点击或触摸屏幕时,光线(单向线)会从点击点(或触摸点)无限向前投射到场景中。它指向的方向由相机面向的方向决定。大多数情况下,您会将其设置为主相机,因为这是玩家将会期待事件的发生。
The reason you need to specify an Event Camera is ray casting. When the user clicks on or touches the screen, a ray (one-directional line) is cast infinitely forward from the point of click (or touch) into the scene. The direction it points is determined by the direction the camera is facing. Most of the time, you will set this as your Main Camera because that is the direction in which the player will expect the events to occur.
当您选择“世界空间”时,以下属性将可用:
When you have World Space selected, the following properties become available:
接下来我们看一下Canvas Scalar组件。
Next, let us look at the Canvas Scalar component.
Canvas Scalar组件决定画布内项目的缩放方式。它还决定像素密度UI Canvas内的项目。
The Canvas Scalar component determines how the items within the canvas will scale. It also determines the pixel density of the items within the UI Canvas.
在第 1 章中,我们讨论了如何以单一分辨率或单一宽高比构建游戏。但是,大多数时候,您无法选择游戏的分辨率或宽高比。您会注意到,我只提到为 PC、Mac 和 Linux 独立版本和 WebGL 版本指定宽高比和分辨率。当您构建将在手持屏幕或电视屏幕上播放的内容时,您无法保证该屏幕的大小。
In Chapter 1, we discussed how to build your game at a single resolution or a single aspect ratio. However, most of the time, you will not have the luxury of choosing the resolution or aspect ratio of your game. You’ll note that I only mentioned specifying the aspect ratio and resolution for the PC, Mac, and Linux Standalone builds and the WebGL builds. When you build to something that will play on a handheld screen or a TV screen, you cannot guarantee how big that screen will be.
由于您无法保证游戏的分辨率或宽高比,因此 UI 需要适应各种分辨率和缩放比例;这就是Canvas Scalar 组件存在的原因。
Due to the fact that you cannot guarantee the resolution or aspect ratio of your game, the need for your UI to adjust to various resolutions and scaling is very important; that’s why this Canvas Scalar component exists.
Canvas Scalar组件有四种UI缩放模式:
The Canvas Scalar component has four UI Scale Modes:
前三种UI 缩放模式在 Canvas渲染模式设置为屏幕空间-叠加或屏幕空间-相机时可用。第四种UI 缩放模式在Canvas渲染模式设置为世界空间(当放)。
The first three UI Scale Modes are available when the Canvas Render Mode is set to Screen Space-Overlay or Screen Space-Camera. The fourth UI Scale Mode is automatically assigned when the Canvas Render Mode is set to World Space (it cannot be changed when set).
当画布的UI 缩放模式设置为恒定像素大小时,UI 中的每个项目都将保持其原始像素无论屏幕大小如何,尺寸都会缩放。您会注意到这是默认设置,因此默认情况下 UI 不会缩放;您必须打开该设置才能通过更改屏幕分辨率进行缩放。
When a Canvas has the UI Scale Mode set to Constant Pixel Size, every item in the UI will maintain its original pixel size regardless of the size of the screen. You’ll note that this is the default setting, so by default UI does not scale; you must turn the setting on for it to scale by altering screen resolutions.
图 6.9:恒定像素大小 UI 缩放模式属性
Figure 6.9: Constant Pixel Size UI Scale Mode properties
当您将UI 缩放模式更改为恒定像素大小时,您将看到检查器中出现以下属性:
When you change the UI Scale Mode to Constant Pixel Size, you will see the following properties appear within the inspector:
当您将Canvas Scalar组件设置为“随屏幕尺寸缩放”时,Canvas 上的 UI 元素将根据“参考分辨率”缩放。如果屏幕大于或小于“参考分辨率”值,则 Canvas 上的项目将放大或缩小因此。在第 1 章中,我告诉过你,你应该决定一个代表你的 UI 设计的理想分辨率的默认分辨率。这个默认分辨率将是参考分辨率。
When you have the Canvas Scalar component set to Scale with Screen Size, UI elements on the Canvas will scale based on a Reference Resolution. If the screen is larger or smaller than the Reference Resolution value, the items on the Canvas will then scale up or down accordingly. In Chapter 1, I told you that you should decide on a default resolution that represents the ideal resolution of your UI design. This default resolution will be the Reference Resolution.
图 6.10:按屏幕尺寸缩放的 UI 缩放模式属性
Figure 6.10: Scale With Screen Size UI Scale Mode properties
如果屏幕的宽高比与参考分辨率值匹配,则项目可以毫无问题地放大和缩小。如果不匹配,则需要使用Canvas Scalar组件来定义项目在宽高比发生变化时如何缩放。
If the aspect ratio of your screen matches the Reference Resolution value, then things will scale up and down without any problems. If it does not match, then you need to use the Canvas Scalar component to define how items will scale if the aspect ratio changes.
这可以通过使用屏幕匹配模式设置来实现。以下列表是三种不同的屏幕匹配模式,它们决定了当游戏的宽高比与参考分辨率 宽高比不匹配时画布将如何缩放:
This can be done by using the Screen Match Mode settings. In the following list are the three different Screen Match Modes that determine how the Canvas will scale if the game’s aspect ratio does not match the Reference Resolution aspect ratio:
扩展和收缩屏幕匹配模式没有其他属性可供编辑;它们只是“做自己的事情”。但是,匹配宽度或高度屏幕匹配模式确实有一个匹配属性。此属性是一个滑动比例,可以在0(宽度)和1(高度)之间调整。
The Expand and Shrink Screen Match Modes do not have any further properties to edit; they just “do their own thing.” However, the Match Width Or Height Screen Match Mode does have a Match property. This property is a sliding scale that can be adjusted between 0 (Width) and 1 (Height).
图 6.11:按屏幕尺寸缩放的 UI 缩放模式属性
Figure 6.11: Scale With Screen Size UI Scale Mode properties
当Match的值设置为0时,Canvas Scaler将强制 Canvas 始终具有由Reference Resolution指定的相同宽度。这将保持对象沿 Canvas 宽度的相对比例和位置。因此,对象在水平方向上不会彼此远离或靠近。但是,它将完全忽略高度。因此,对象可以在垂直方向上彼此远离或靠近。
When the value of Match is set to 0, the Canvas Scaler will force the Canvas to always have the same width specified by the Reference Resolution. This will maintain the relative scales and positions of objects along the width of the Canvas. So, objects will not get further away from or closer to each other in the horizontal direction. However, it will completely ignore the height. So, objects can get further from or closer to each other in the vertical direction.
将匹配值设置为1将完成相同的操作,但将保持对象沿高度而不是宽度的位置和比例。
Setting the Match value to 1 will accomplish the same thing but will maintain the positions and scales of the objects along the height, not the width.
将匹配值设置为0.5会将游戏的宽度和高度与参考分辨率进行比较,并尝试在水平和垂直方向上保持物体之间的距离。
Setting the Match value to 0.5 will compare the game’s width and height to that of Reference Resolution, and it will try to maintain the distances between objects in both the horizontal and vertical directions.
Match值可以是0到1之间的任意数字。如果数字接近1 ,则缩放将有利于高度,如果数字接近0,则缩放将有利于宽度。
The Match value can be any number between 0 and 1. If the number is closer to 1, scaling will favor the height, and if it is closer to 0, it will favor the width.
这些Match设置并非适合所有游戏的所有宽高比和分辨率。您可以选择将取决于您希望 UI 如何缩放。如果您希望保持相对垂直位置,请使用高度( 1 )。如果您希望保持相对水平位置,请使用宽度( 0 )。这实际上取决于您最关心哪个间距。
None of these Match settings will be perfect for all games at all aspect ratios and resolutions. The settings you choose will depend on how you want the UI to scale. If you want the relative vertical positions to be maintained, use Height (1). If you want the relative horizontal positions to be maintained, use Width (0). It really just depends on which spacing you care the most about.
我建议根据您的游戏方向使用以下设置:
I recommend using the following settings based on the orientation of your game:
|
方向 Orientation |
匹配值 Match Value |
|
肖像 Portrait |
0(宽度) 0 (Width) |
|
景观 Landscape |
1(高度) 1 (Height) |
|
各不相同 Varies |
0.5(宽度和高度) 0.5 (Width and Height) |
表 6.1:方向和匹配值
Table 6.1: Orientation and Match Value
我根据参考分辨率上的两个数字中最小的一个来选择这些设置。在纵向模式下,宽度将最小,因此我发现保持项目在宽度上的相对位置很重要。这是个人偏好,只是建议,不一定适用于所有游戏。但是,我发现这对大多数游戏来说都是一个很好的经验法则。
I chose these settings based on whichever of the two numbers on the Reference Resolution is the smallest. In Portrait mode, the width will be the smallest, so I find it important to maintain the relative position of the items in the width. This is a personal preference and just a recommendation, and it will not necessarily make sense for all games. However, I have found it to be a good rule of thumb for most games.
最好避免制作在纵向和横向模式之间变化的游戏,除非您拥有最少的 UI 或非常擅长创建可扩展的 UI。
It is best to avoid making games that will vary between portrait and landscape mode unless you have minimal UI or are very comfortable with creating scalable UI.
当画布的UI 缩放模式设置为恒定物理尺寸时,无论屏幕尺寸如何,UI 中的每个项目都将保持其原始物理尺寸。物理尺寸指的是将显示的尺寸就像用户拿出尺子并在屏幕上测量一样。与“恒定像素大小”非常相似,具有此UI 缩放模式设置的画布上的项目不会缩放。
When a Canvas has the UI Scale Mode set to Constant Physical Size, every item in the UI will maintain its original physical size, regardless of the size of the screen. Physical size references the size that will appear to the user if they were to take out a ruler and measure it on their screen. Much like Constant Pixel Size, items on Canvases with this UI Scale Mode setting will not scale.
如果您希望某个 UI 项目始终具有特定的宽度和高度,则可以将其放在具有此UI 缩放模式的 Canvas 上。例如,如果您希望按钮始终为 2 英寸宽和 1 英寸高,则可以使用此模式。这在手机游戏中特别有用,它允许您根据我们在第 4 章中讨论的建议确保按钮的尺寸足够大,适合人类手指。
If you had a UI item that you wanted to always be a specific width and height, you’d put it on a Canvas that has this UI Scale Mode. For example, if you wanted a button to always be 2 inches wide and 1 inch tall, you’d use this mode. This is particularly helpful in mobile games, allowing you to make sure buttons are sized large enough for human fingers based on the recommendations we discussed in Chapter 4.
图 6.12:使用恒定物理尺寸缩放 UI 缩放模式属性
Figure 6.12: Scale with Constant Physical Size UI Scale Mode properties
当您将UI 缩放模式更改为恒定物理尺寸时,您将看到检查器中出现以下属性:
When you change the UI Scale Mode to Constant Physical Size, you will see the following properties appear within the inspector:
世界是唯一适用于画布的UI 缩放模式设置为World Space。从以下屏幕截图中你会看到该模式无法更改:
World is the only UI Scale Mode available for Canvases set to World Space. You’ll see from the following screenshot that the mode cannot be changed:
图 6.13:使用 World UI Scale Mode 属性进行缩放
Figure 6.13: Scale with World UI Scale Mode properties
当您将UI Scale Mode更改为World时,您将看到检查器中出现以下属性:
When you change the UI Scale Mode to World, you will see the following property appear within the inspector:
Graphic Raycaster组件允许您检查 Canvas 上的对象是否已被用户输入击中使用事件系统。正如在查看世界空间画布渲染模式时所讨论的那样,当用户触摸屏幕,从玩家触摸的屏幕点向前投射一条射线。Graphic Raycaster检查这些射线,看看它们是否击中了画布上的某个物体。
The Graphic Raycaster component allows you to check to see whether objects on the Canvas have been hit by a user input using the Event System. As discussed when looking at the World Space Canvas Render Mode, when a user touches the screen, a ray is cast forward from the point on the screen at which the player touches. The Graphic Raycaster checks these rays and sees if they hit something on the Canvas.
图 6.14:Graphic Raycaster 组件
Figure 6.14: The Graphic Raycaster component
您可以在Graphic Raycaster组件上调整以下属性:
You can adjust the following properties on the Graphic Raycaster component:
图 6.15:Graphics Raycaster Blocking Objects 选项
Figure 6.15: Graphics Raycaster Blocking Objects options
图 6.16:图形光线投射阻挡遮罩选项
Figure 6.16: Graphics Raycaster Blocking Mask options
我们将讨论光线投射和在第 8 章中更详细地介绍了事件系统。
We will discuss Raycasting and the Event System more thoroughly in Chapter 8.
Canvas Renderer组件是不是在 Canvas GameObject 上,而是在所有可渲染的UI 对象。
The Canvas Renderer component is not on a Canvas GameObject but on all renderable UI objects.
图 6.17:Canvas Renderer 组件
Figure 6.17: The Canvas Renderer component
要渲染 UI 元素,它必须具有Canvas Renderer组件。您通过+ | UI菜单创建的所有可渲染 UI 元素都会自动附加此组件。如果您尝试删除此组件,您将看到类似以下内容的警告:
For a UI element to render, it must have a Canvas Renderer component on it. All the renderable UI elements that you create via the + | UI menu will automatically have this component attached to them. If you try to remove this component, you will see a warning similar to the following:
图 6.18:尝试删除 Canvas Renderer 组件时出现的警告消息
Figure 6.18: Warning message when you try to remove a Canvas Renderer component
在上面的截图中,我尝试从 Text UI 对象中删除 Canvas Renderer 组件。如您所见,它不允许我删除 Canvas Renderer 组件,因为文本组件依赖它。如果我返回并删除 Text 组件,然后我就可以删除 Canvas Renderer 组件。
In the preceding screenshot, I tried to remove the Canvas Renderer component from a Text UI object. As you can see, it will not let me remove the Canvas Renderer component because the Text component relies on it. If I return and remove the Text component, I would then be able to remove the Canvas Renderer component.
Canvas Renderer组件上的唯一属性是Cull Transparent Mesh切换。“To cull”表示不绘制。因此,此属性表示渲染器不会绘制顶点颜色 alpha 为 0 或接近0 的任何几何图形。
The only property on the Canvas Renderer component is the Cull Transparent Mesh toggle. “To cull” means to not draw. So, this property states that the renderer will not draw any geometry that has its vertex color alpha at or close to 0.
UI 面板的主要功能是容纳其他 UI 元素。您可以通过选择+ | UI | Panel来创建面板。重要的是请注意,没有 Panel 组件。Panel 实际上只是具有Rect Transform、Canvas Renderer和Image组件的游戏对象。因此,UI Panel 实际上只是一个带有一些预定义属性的 UI Image 。
The main function of UI Panels is to hold other UI elements. You can create a Panel by selecting + | UI | Panel. It’s important to note that there is no Panel component. Panels are really just GameObjects that have Rect Transform, Canvas Renderer, and Image components. So, really, a UI Panel is just a UI Image with a few properties predefined for it.
图 6.19:Panel GameObject 上的组件
Figure 6.19: The components on a Panel GameObject
默认情况下,面板以背景图像(只是一个灰色圆角矩形)作为具有中等不透明度的源图像。您可以用另一幅图像替换源图像或完全删除该图像。
By default, Panels start with the Background Image (which is just a grey rounded rectangle) as the Source Image with medium opacity. You can replace the Source Image with another Image or remove the image entirely.
面板非常有用,当你尝试确保项目能够缩放并相对于彼此适当定位。包含在同一个面板内的项目将相对于面板缩放,并在此过程中保持彼此的相对位置。
Panels are very useful when you are trying to ensure that items scale and are appropriately positioned relative to each other. Items that are contained within the same Panel will scale relative to the Panel and maintain their relative position to each other in the process.
我们很快会更彻底地了解图像组件,但是现在我们正在查看一个允许我们编辑其矩形变换组件的对象,让我们探索该组件。
We will look at the Image component more thoroughly soon, but now that we are looking at an object that will allow us to edit its Rect Transform component, let›s explore that component.
每个 UI 元素都有一个Rect Transform组件。Rect Transform组件的工作原理与Transform组件非常相似,用于确定对象的位置它所附着于其上。
Each UI element has a Rect Transform component. The Rect Transform component works very similarly to the Transform component and is used to determine the position of the object on which it is attached.
任何变换工具都可用于操作 UI 对象。不过,矩形工具可让您缩放、移动和通过操纵矩形来旋转任何物体涵盖了它。虽然此工具可以用于 3D 对象,但它对 2D 和UI 对象最有用。
Any of the Transform tools can be used to manipulate UI objects. However, the Rect Tool allows you to scale, move, and rotate any object by manipulating the rectangle that encompasses it. While this tool can be used with 3D objects, it is most useful for 2D and UI objects.
图 6.20:矩形工具
Figure 6.20: The Rect Tool
使用矩形工具时,选择正确的定位模式非常重要。您可以选择Center或Pivot以及Global或Local。单击以下按钮可切换模式:
When using the Rect Tool, it is important that you have the correct positioning modes selected. You can select Center or Pivot and Global or Local. The modes will toggle by clicking on the buttons:
图 6.21:各种定位模式
Figure 6.21: The various positioning modes
下图显示了全局和局部模式下面板的矩形变换的边界框。空心蓝色圆圈表示对象的枢轴点:
The following illustration shows the bounding boxes of the Rect Transform for a Panel in Global and Local modes. The empty blue circle represents the object’s pivot point:
图 6.22:全局模式与局部模式
Figure 6.22: Global mode vs. Local mode
接下来我们看一下Rect Transform组件。
Next, let’s look at the Rect Transform component.
如前所述,UI 元素没有标准Transform组件;它们具有Rect Transform组件。如果如果你将它与标准Transform组件进行比较,你会发现它具有相当多的属性:
As stated earlier, UI elements do not have the standard Transform component; they have the Rect Transform component. If you compare it to a standard Transform component, you will see that it has quite a few more properties:
图 6.23:变换与矩形变换
Figure 6.23: Transform vs. Rect Transform
您可以使用它来更改位置、旋转和缩放,就像使用Transform一样,但还增加了Anchor Presets属性(由左上角的方形图像表示)、两个用于确定尺寸的值(Anchor Min和Max点)和Pivot点。需要注意的是,Scale值被视为局部比例。如果您使用 Rect Tool 重新缩放对象,即使使用 Local 定位模式, Scale中的值仍将保持为1。
You can use it to change the position, rotation, and scale, just as you can with Transform, but there are the added properties of Anchor Presets (represented by the square image in the top left corner), two values for determining dimension (Anchor Min and Max points), and the Pivot point. It’s important to note that the Scale value is considered the local scale. If you rescale the object with the Rect Tool, even with the Local positioning mode, the values within Scale will remain at 1.
你可能已经注意到上图中的位置和尺寸值标签与UI 面板属性部分提供的标签不同。这是因为表示位置和尺寸的标签会根据所选的锚点预设而变化。我们稍后将讨论如何使用这些锚点预设,但让我们看一些位置和尺寸值可以容纳的标签的不同示例。
You may have noticed that the labels for the position and dimensional values are different in the preceding illustration than they are in the one provided in the UI Panel Properties section. This is because the labels that represent the position and dimension change depending on the Anchor Preset chosen. We’ll discuss how to use these Anchor Presets momentarily, but let’s look at some different examples of labels the position and dimensional values can hold.
图 6.24:矩形变换属性的变化
Figure 6.24: Variation in Rect Transform properties
如果Rect Transform的 Anchor Preset 设置为不包含拉伸,如上图左上角的示例,位置的值由Pos X、Pox Y、Pos Z决定,尺寸由Width 和Height决定。
If the Rect Transform has its Anchor Preset set to not include stretch, as with the top-left example in the preceding screenshot, the values for position are determined by Pos X, Pox Y, Pos Z, and the dimensions are determined by Width and Height.
如果Rect Transform的 Anchor Preset 设置为包含拉伸,与其他三个示例一样,则垂直于拉伸的位置和平行于拉伸的尺寸将标记为Left、Right、Top和Bottom 。这些值表示与父级Rect Transform边框的偏移量。
If the Rect Transform has its Anchor Preset set to include stretch, as with the other three examples, the positions perpendicular to the stretch and the dimensions parallel to the stretch are labeled with Left, Right, Top, and Bottom. These values represent the offset from the border of the parent’s Rect Transform.
对象的锚点决定了所有相对位置的测量起点。枢轴点决定了缩放和旋转修改器的测量起点发生。它将围绕该点旋转并朝该点缩放。我们将在“锚点和枢轴”部分更彻底地研究锚点和枢轴。
The Anchor point of an object determines the point from which all the relative positions are measured from. The Pivot point determines the point from which the scaling and rotating modifiers happen. It will rotate around this point and scale toward this point. We will look at Anchors and Pivots more thoroughly in the Anchors and Pivot section.
有两种不同的编辑模式可供选择在Rect Transform组件中,您可以看到蓝图模式和 Raw Edit 模式,分别由以下图标表示:
There are two different edit modes available to you within the Rect Transform component—Blueprint mode and Raw Edit mode—as represented by the following icons, respectively:
图 6.25:矩形变换编辑模式
Figure 6.25: Rect Transform edit modes
蓝图模式将忽略对其应用的任何局部旋转或缩放,并将矩形变换边界框显示为未旋转、未缩放的框。以下屏幕截图显示了在关闭和打开蓝图模式的情况下旋转和缩放的面板边界框:
The Blueprint mode will ignore any local rotation or scaling applied to it and will display the Rect Transform bounding box as a non-rotated, non-scaled box. The following screenshot shows the bounding box of a Panel that has been rotated and scaled with Blueprint mode turned off and turned on:
图 6.26:蓝图模式开启与蓝图模式关闭
Figure 6.26: Blueprint mode on vs. Blueprint mode off
原始编辑模式将允许您更改 UI 对象的锚点和枢轴点,而无需对象移动或根据您所做的更改进行扩展。
The Raw Edit mode will allow you to change the anchor and pivot points of a UI object without the object moving or scaling based on the changes you have made.
每个 UI 对象都有锚点和枢轴点。当一起使用时,它们将有助于确保您的 UI 定位正确,并在游戏的分辨率或宽高比发生变化时适当缩放。
Every UI object has Anchor Handles and a Pivot Point. When used together, they will help ensure that your UI is positioned appropriately and scales appropriately if the resolution or aspect ratio of your game changes.
The Anchor Handles are represented by four triangles in the form of an X, as shown in the following diagram:
图 6.27:锚柄和枢轴点
Figure 6.27: Anchor Handles and Pivot Points
锚可以位于一组组成一个 Anchor,如上图所示图中,也可以拆分成多个Anchor,如下:
The Anchors can be in a group together forming a single Anchor, as shown in the preceding diagram, or they can be split into multiple Anchors, as follows:
图 6.28:拆分锚柄
Figure 6.28: Splitting Anchor Handles
锚点将始终形成一个矩形。因此,边将始终对齐。
The Anchors will always form a rectangle. So, the sides will always line up.
矩形变换具有锚点最小值和锚点最大值属性。这些属性以百分比形式表示锚点手柄相对于父级矩形变换的位置。例如,x值中的0将手柄一直移到左边,1将移动一直向右移动手柄。从以下屏幕截图中,您可以看到如何调整x值将锚点相对于父级左右移动:
The Rect Transform has properties for Anchor Min and Anchor Max points. These represent the position of the Anchor Handles relative to the parent’s Rect Transform as percentages. For example, a 0 in an x value moves the handles all the way to the left, and a 1 moves the handles all the way to the right. You can see from the following screenshots how adjusting the x value will move the anchor left and right relative to the parent:
图 6.29:调整锚点的最小值和最大值
Figure 6.29: Adjusting Anchor Min and Max
矩形变换具有锚点预设和锚点 最小点和最大点的属性。锚点表示 UI 元素与其父级的 Rect Transform 的连接点:
The Rect Transform has properties for Anchor Presets and Anchors Min and Max points. The Anchor represents the point at which the UI element is connected to its parent’s Rect Transform:
图 6.30:访问锚点预设
Figure 6.30: Accessing the Anchor Presets
由于 Canvas 没有父级,因此您会看到 Anchor Preset 区域为空。无论 Canvas 是什么,情况都是如此选择渲染模式:
As a Canvas has no parent, you’ll see that the Anchor Preset area is empty. This is true regardless of the Canvas Render Mode chosen:
图 6.31:画布游戏对象上缺少锚点预设
Figure 6.31: Lack of Anchor Presets on a Canvas GameObject
单击“Anchor Presets”框将显示所有可能的“Anchor Presets”的列表,如以下屏幕截图所示:
Clicking on the Anchor Presets box will display a list of all the possible Anchor Presets, as shown in the following screenshot:
图 6.32:所有可用的 Anchor Presents
Figure 6.32: All available Anchor Presents
如果你点击其中一个预设,它会将锚点移动到屏幕截图。您还可以使用锚点预设来调整位置和枢轴点。
If you click on one of the presets, it will move the anchors to the position displayed in the screenshot. You can also adjust the position and pivot point using the anchor preset.
如果按住Shift和/或Alt,表示预设的图像将会改变。按住Shift将显示以蓝点表示的枢轴点的位置,按住Alt将显示位置的变化方式,按住两者将显示枢轴点和位置变化。
The images representing the presets will change if you hold down Shift and/or Alt. Holding Shift will show the positions for the pivot point represented by blue dots, holding Alt will show how the position will change, and holding both will show the pivot point and the position change.
图 6.33:设置枢轴和位置
Figure 6.33: Setting pivot and position
笔记
Note
如果使用 Mac,由于没有Alt键,您将改用Option键。但是,说明仍会在编辑器中显示Alt,并且 Mac 版本不会发生改变。之前的屏幕截图是在 Mac 上拍摄的,尽管它没有Alt键。
If using a Mac, since there is no Alt key, you will use the Option key instead. However, the instructions will still say Alt in the Editor and this does not change for the Mac version. The previous screenshots were taken on a Mac, despite it having no Alt key.
Now, let’s look at the Canvas Group component.
您可以将Canvas Group组件添加到任何 UI 对象。将其附加到 UI 对象将允许您调整使用单个组件来管理对象及其所有子对象,而不必为每个UI 元素调整这些属性。
You can add a Canvas Group component to any UI object. Attaching it to a UI object will let you adjust the specific properties of the object as well as all of its children with a single component rather than having to adjust these properties for each of the UI elements.
您可以通过从 UI对象的检查器中选择添加组件|布局|画布组(您也可以只搜索画布组)将画布组组件添加到任何 UI 对象。
You can add a Canvas Group component to any UI object by selecting Add Component | Layout | Canvas Group (you can also just search for Canvas Group) from the UI object›s Inspector.
图 6.34:画布组组件
Figure 6.34: The Canvas Group component
您可以使用Canvas Group组件调整以下属性:
You can adjust the following properties using a Canvas Group component:
不使用文本或图像很难制作任何 UI 示例。因此,在介绍布局示例之前,让我们先看看了解 UI Text 和 UI Image GameObjects 的基本属性。UI Text 和 UI Images第 11 章和第 12 章将对此进行更详细的讨论。
It’s kind of hard to make any UI examples without using text or images. So, before we cover examples of layouts, let’s first look at the basic properties of the UI Text and UI Image GameObjects. UI Text and UI Images are discussed more thoroughly in Chapter 11 and Chapter 12.
当您使用+ | UI | Text创建新的 Text 对象时,您将看到它有一个Text组件。
When you create a new Text object using + | UI | Text, you will see that it has a Text Component.
图 6.35:文本组件
Figure 6.35: The Text component
您可以通过更改显示的文本来更改文本框中的单词。在第 11 章中,我们将仔细研究Text组件的各个属性,但是现在,大多数属性的作用应该是相当明显的。
You can change the displayed text by changing the words in the Text box. In Chapter 11, we’ll take a closer look at the individual properties of the Text component, but for now, it should be fairly obvious what most of the properties do.
当您使用+ | UI | Image创建新的 Image 对象时,您将看到它有一个Image组件。
When you create a new Image object using + | UI | Image, you will see that it has an Image component.
图 6.36:图像组件
Figure 6.36: The Image component
请记住,面板是本质上是一个图像,但预先填充了一些属性。当你创建图像,但是,没有预填充的属性。
Remember that a Panel is essentially an Image but with a few properties prefilled. When you create an Image, however, there are no prefilled properties.
在本章中,我们将使用“源图像”属性,该属性允许您更改显示的精灵。我们将在第 11 章中介绍其他属性。
We’ll work with the Source Image property in this chapter, which allows you to change the displayed sprite. We’ll look at the other properties in Chapter 11.
现在让我们看一些例子!我们将创建一个基本的抬头显示器( HUD )布局和一个背景图像它可以随屏幕拉伸并以多种分辨率缩放。
Now let’s jump into some examples! We’ll be creating a layout for a basic heads-up-display (HUD) and a background image that stretches with the screen and scales at multiple resolutions.
在我们开始构建 UI 之前,让我们先设置一下项目并引入我们需要的艺术资产。
Before we begin building our UI, let’s set up our project and bring in the art assets we will need.
我们将从设置我们的项目开始:
We’ll begin by setting up our project:
笔记
Note
我们选择 2D 模式,因为它将使我们的 UI 精灵导入更加容易。在 2D 模式下,所有图像都导入为精灵(2D 和 UI)图像,而不是像在 3D 模式下那样导入为纹理图像。您可以随时通过导航至编辑|项目设置|编辑器并将模式更改 为3D来更改为 3D 模式。
We’re selecting 2D Mode because it will make importing our UI sprites a lot easier. When in 2D Mode, all images import as Sprite (2D and UI) images rather than Texture images, as they do in 3D Mode. You can change to 3D Mode at any time by navigating to Edit | Project Settings | Editor and changing Mode to 3D.
我们将使用艺术资产我根据以下站点上的免费艺术资产进行了修改:
在文本源文件的Chapter2/Sprites文件夹中,找到catSprites.png、pinkBackground.png和uiElements.png图像,然后将它们拖到Sprites文件夹中导入到项目中。
We’ll be using art assets that I’ve modified from free art assets found at the following sites:
In the Chapter2/Sprites folder of the text’s source files, locate the catSprites.png, pinkBackground.png, and uiElements.png images and import them into your project by dragging them into the Sprites folder.
图 6.37:导入精灵
Figure 6.37: Importing the sprites
图 6.38:选择两个精灵表
Figure 6.38: Selecting both sprite sheets
图 6.39:将精灵转换为多精灵模式
请注意,检查器显示2 Texture 2Ds Import Settings,因为我们选择了两张图像。
Figure 6.39: Converting the sprites to Multiple Sprite Mode
Note that the Inspector says 2 Texture 2Ds Import Settings because we have selected two images.
图 6.40:拆分后的精灵表
Figure 6.40: The split sprite sheet
现在我们已经设置好了项目和精灵,我们可以开始使用UI 示例了。
Now that we have our project and sprites set up, we can begin with the UI examples.
我们将制作如下所示的HUD :
We will make a HUD that will look like the following:
图 6.41:我们将要开发的 HUD
Figure 6.41: The HUD we will develop
它将在接下来的章节中得到扩展,但目前,它将有一个非常简单的布局,重点关注父子关系以及锚点/枢轴点位置。
It will be expanded upon in the upcoming chapters, but for now, it’ll have a pretty simple layout that will focus on parent–child relationships and anchor/pivot point placement.
要创建上图所示的HUD,请完成以下步骤:
To create the HUD shown in the preceding image, complete the following steps:
图 6.42:画布缩放器属性
我们已将屏幕匹配模式设置为匹配宽度或高度,并将匹配设置设置为1,以便它在垂直方向上保持比例。如果您还记得“按屏幕尺寸缩放”部分的内容,我发现这对大多数使用横向分辨率制作的游戏最有效。
Figure 6.42: The Canvas Scaler properties
We have set the Screen Match Mode to Match Width Or Height, with the Match settings set to 1 so that it maintains the ratios in the vertical direction. If you remember from the Scale with Screen Size section, I find that this works best for most games made with a landscape resolution.
图 6.43:1024 x 768 游戏视图分辨率
Figure 6.43: 1024 x 768 Game view resolution
图 6.44:设置HUD面板的锚点预设
Figure 6.44: Set the Anchor Preset of the HUD Panel
图 6.45:设置HUD面板的锚点预设
Figure 6.45: Set the Anchor Preset of the HUD Panel
图 6.46:分配了 uiElements_1 的图像组件
Figure 6.46: The Image component with uiElements_1 assigned
图 6.47:将颜色选择器上的 alpha 值调整为完整 alpha
Figure 6.47: Adjusting the alpha value on the color picker to full alpha
面板现在将仅占据场景的顶部:
The Panel will now take up only the top portion of the scene:
图 6.48:保留纵横比后的面板
Figure 6.48: The Panel after preserving the aspect ratio
图 6.49:超出可见图像区域的矩形变换
Figure 6.49: The Rect Transform exceeding the visible image area
图 6.50:重新缩放 HUD 面板的矩形变换
Figure 6.50: Rescaling the Rect Transform of the HUD Panel
让我们从包含猫角色头部的图像开始。右键单击HUD 面板并选择UI |图像。你会看到它是HUD 面板的子项。将其重命名为Character Holder。
Let’s start with the Image that holds the cat character’s head. Right-click on the HUD Panel and select UI | Image. You’ll see that it is a child of the HUD Panel. Rename it Character Holder.
图 6.51:重新缩放角色支架的矩形变换
Figure 6.51: Rescaling the Rect Transform of the Character Holder
图 6.52:重新缩放字符图像的矩形变换
由于我们确保角色支架的矩形变换紧密贴合支架的图像,因此它应该可以使猫的头完美地贴合支架图像,而无需调整任何设置!
图 6.53:角色图像装入角色支架中
Figure 6.52: Rescaling the Rect Transform of the Character Image
Since we ensured that we have the Rect Transform of the Character Holder fit snuggly around the holder›s image, it should make the cat›s head fit perfectly within the holder image without having to adjust any settings!
Figure 6.53: The Character Image fitting within the Character Holder
图 6.54:Health Holder 的属性
Figure 6.54: The properties of the Health Holder
图 6.55:Health Holder 的属性
请注意,添加了一些填充,以便您可以看到Health Holder的边缘。
Figure 6.55: The properties of Health Holder
Note that a little padding was added so that you can see the edges of Health Holder.
图 6.56:矩形变换定位模式
Figure 6.56: The Rect Transform Positioning mode
图 6.57:移动枢轴点
Figure 6.57: Moving the pivot point
图 6.58:调整生命条的比例
Figure 6.58: Adjusting the scale of the health bar
这就是我们的 HUD 示例!尝试将游戏视图的宽高比更改为不同的设置,以便你可以看到面板适当缩放并查看所有保持的对象相对位置。
That’s it for our HUD example! Try changing your Game view’s aspect ratio to different settings so that you can see the Panel scale appropriately and see all the object-relative positions maintained.
如果您在更改游戏的宽高比时 HUD 出现一些异常,请确保您的对象具有正确的父子关系。您的父子关系应如下:
If your HUD is doing some wonky stuff when you change the Game’s aspect ratio, ensure that your objects have the correct parent–child relationship. Your parent–child relationships should be as follows:
图 6.59:层次结构的父子关系
Figure 6.59: The Hierarchy’s parent–child relationship
Also, check to ensure that the anchor and pivot points are set correctly.
放置背景只要使用适当的 Canvas 属性,绘制随屏幕缩放的图像并不困难。我们将扩展我们的 HUD 示例并在场景中放置背景图像。
Placing a background image that scales with the screen is not too difficult as long as you use the appropriate Canvas properties. We will expand upon our HUD example and place a background image in the scene.
图 6.60:背景图像的结果
Figure 6.60: The result of the background image
我们需要确保该背景图像不仅显示在其他 UI 元素后面,还显示在我们可能放置在场景中的任何游戏对象后面。
We’ll need to ensure that this background image doesn’t just display behind other UI elements but also displays behind any game objects we may put in our scene.
要制作显示在所有 UI 元素以及所有游戏元素后面的背景图像,请完成以下步骤:
To make a background image that displays behind all UI elements as well as all game elements, complete the following steps:
图 6.61:背景画布的画布组件
Figure 6.61: The Canvas component of the Background Canvas
图 6.62:编辑图层
Figure 6.62: Editing Layers
图 6.63:添加背景排序层
对图层进行排序的原理是,位于此列表顶部的图层将渲染场景中最远的图层。因此,如果您想添加前景图层,请将其添加到 Default 下方。Default是所有新精灵都会自动添加到的图层,因此,除非您创建新图层,否则Background图层将位于您创建的任何新精灵的后面。如果您确实创建了新图层,请确保Background图层位于此列表的顶部。
Figure 6.63: Adding the Background Sorting Layer
Sorting layers work so that whichever is on the top of this list will render the furthest back in the scene. So, if you wanted to add a foreground layer, you’d add it below Default. Default is the layer that all new sprites will automatically be added to, so unless you create new layers, the Background layer will be behind any new sprite you create. If you do create new layers, ensure that the Background layers stay on the top of this list.
图 6.64:设置背景排序层
Figure 6.64: Setting the Background Sorting Layer
图 6.65:背景图像设置
Figure 6.65: The Background Image settings
由于背景画布设置为屏幕空间 - 相机,您可能需要更改视图才能看到它。它将位于主相机视图所在的位置。
Because the Background Canvas is set to Screen Space – Camera, you may have to change your view so that you can see it. It will be where the Main Camera view is.
就是这样!尝试更改调整游戏视图的宽高比,并在自由宽高比模式下调整屏幕大小,以便您可以看到背景图像始终填满屏幕。此外,尝试将一些非 UI 2D 精灵添加到场景中,看看它们如何在背景上渲染。
That’s it! Try changing the Game view’s aspect ratio around and resizing the screen in Free Aspect mode so that you can see the background image always filling the screen. Also, try adding some non-UI 2D sprites to the scene and see how they render on top of the background.
此示例的一个缺点是允许背景图像改变其宽高比。您会发现由于这个原因,图像在某些宽高比下看起来很糟糕。对于以多种宽高比发布的游戏来说,此背景图像不是一个好选择。我选择此图像有两个原因:
One thing that is not ideal about this example is that the background image is being allowed to change its aspect ratio. You’ll see that the image looks pretty bad at some aspect ratios because of this. This background image will not be a good choice for a game that would be released on multiple aspect ratios. I chose this image for two reasons:
我强烈建议使用此方法创建背景图像,使用不会明显显示失真的图案。
I highly recommend that if you use this method to create a background image, you use one with a pattern that doesn’t so obviously display distortion.
本章我们将介绍的最后一个例子将使用Canvas Group组件。直到第 8 章开始编程时,我们才能真正看到此组件的实际作用,但现在我们可以打下基础。我们还将通过此示例进行更多布局 UI 的练习。
The last example we will cover in this chapter will utilize the Canvas Group component. We won›t be able to really see this component in action until we start programming in Chapter 8, but we can lay the groundwork now. We’ll also get a bit more practice with laying out UI with this example.
图 6.66:我们将要布局的弹出面板
Figure 6.66: The pop-up Panel we will lay out
要创建如上图所示的弹出菜单,请完成以下步骤:
To create the pop-up menu shown in the preceding image, complete the following steps:
图 6.67:暂停面板的属性
Figure 6.67: The properties of Pause Panel
图 6.68:暂停横幅的属性
我们将在后面的章节中向该横幅添加文本。
Figure 6.68: The properties of Pause Banner
We’ll add the text to this banner in a later chapter.
选择暂停面板,然后选择添加组件|布局|画布组(您也可以直接搜索画布组)。
Select Pause Panel, and then select Add Component | Layout | Canvas Group (you can also just search for Canvas Group).
现在就是这样!在“暂停面板画布组”组件的检查器中更改Alpha值,您将看到,随着更改它,“暂停面板”和“暂停横幅”的Alpha 值都会发生变化。这对于您想要隐藏和显示的弹出菜单非常有用,而无需单独编程每个项目。一旦我们花费 使用暂停面板的时间越长,它上面就会包含更多项目,而且我们会很高兴不必对每个部分进行单独编程。
That’s it for now! Change the values of Alpha in the Inspector of the Pause Panel Canvas Group component, and you will see that as you change it, both the Pause Panel and the Pause Banner alpha values change. This is great for pop-up menus you want to hide and show without having to program each item individually. Once we spend more time with Pause Panel, it will have a lot more items on it, and we will be happy that we don’t have to program each piece individually.
哇!这一章太精彩了!有很多内容需要讲解,因为这一章为本书的其余部分奠定了基础。我们讨论了 Canvas 的概念以及如何在场景中正确定位它。此外,我们还讨论了基本的 UI 面板,以便我们探索在场景中定位 UI 元素的概念。正确设置 Canvas 及其标量是开发 UI 的重要第一步。
Wow! This chapter was intense! There was a lot to cover, as this chapter set the groundwork that will be used throughout the rest of this book. We discussed the concept of a Canvas and how to correctly position it within your scene. Additionally, we discussed the basic UI Panel to allow us to explore the concept of positioning UI elements within a scene. Correctly setting up Canvases and their scalars is an important first step in developing UI.
下一章将介绍如何创建不同的自动布局,让我们能够以网格形式排列 UI。
The next chapter will cover how to create different automatic layouts that will let us line up our UI in grids.
现在我们已经掌握了使用 Rect Transform 和锚点手动定位、缩放和对齐 UI 元素的基础知识,我们可以探索如何使用自动布局。自动布局允许您对 UI 元素进行分组,以便它们可以自动相对于彼此定位。
Now that we have the basics of manually positioning, scaling, and aligning UI elements with the Rect Transform and anchors, we can explore how to use automatic layouts. Automatic layouts allow you to group your UI elements so that they will position automatically relative to each other.
在很多情况下,您会希望 Unity 自动控制 UI 对象的布局。如果您通过代码生成 UI 项目,并且项目数量可能会发生变化,但您仍希望它们正确排列、缩放和定位,则可以使用自动布局。此外,如果您想要完美间隔的 UI 对象,自动布局将帮助您创建这种完美间距,而无需您自己进行任何位置计算。这些自动布局非常适合在网格或列表中对齐的库存系统等。
There are quite a few scenarios in which you will want Unity to automatically control the layout of your UI objects. If you are generating UI items via code and the number of items may change, but you still want them to line up, scale, and position properly, you can use automatic layouts. Also, if you want perfectly spaced UI objects, automatic layouts will help you create this perfect spacing without having to do any position calculating yourself. These automatic layouts work well for things like inventory systems aligned in a grid or list.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
笔记
Note
本节中显示的所有示例都可以在代码包中名为Chapter 07 .unitypackage的 Unity 包中找到。每个示例图像都有一个标题,说明场景中的示例编号。在场景中,每个示例都在其自己的 Canvas 上,并且一些 Canvas 已停用。要在已停用的 Canvas 上查看示例,只需在Inspector中选中Canvas 名称旁边的复选框即可。
All the examples shown in this section can be found within the Unity package named Chapter 07.unitypackage, within the code bundle. Each example image has a caption stating the example number within the scene. In the scene, each example is on its own Canvas, and some of the Canvases are deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector.
图 7.1:用于启用或禁用 Canvas 示例的复选框
Figure 7.1: The checkbox to enable or disable a Canvas example
让我们探索不同类型的自动布局组。
Let’s explore the different types of Automatic Layout Groups.
您可以在此处找到本章节的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2007
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2007
当 UI 对象具有附加到其上的自动布局组组件,其所有子项都会根据布局组件的参数进行对齐、调整大小和定位。自动布局组选项有三种:水平布局组、垂直布局组和网格布局组。
When a UI object has an automatic layout group component attached to it, all of its children will be aligned, resized, and positioned based on the parameters of the layout component. There are three automatic layout group options: Horizontal Layout Group, Vertical Layout Group, and Grid Layout Group.
以下屏幕截图显示了三个面板(由灰色矩形表示),每个面板有六个 UI 图像子项(由黑色矩形表示);第一个面板有一个水平布局组组件,第二个面板有一个垂直布局组组件,第三个面板有一个网格布局组组件:
The following screenshot shows three Panels (represented by gray rectangles), each with six UI Image children (represented by the black rectangles); the first Panel has a Horizontal Layout Group component, the second Panel has a Vertical Layout Group component, and the third Panel has a Grid Layout Group component:
Figure 7.2: Automatic Layout Groups Example 1 in the Chapter7 scene
从上面的截图中,你可以清楚地看到这三种类型的自动布局组的作用。您可以使用这三种类型的任意组合来创建嵌套的、间距完美的布局,如下所示:
From the preceding screenshot, you can see clearly what the three types of automatic layout groups accomplish. You can use any combination of the three to create nested, perfectly spaced layouts, as follows:
图 7.3:第 7 章场景中的自动布局组示例 2
Figure 7.3: Automatic Layout Groups Example 2 in the Chapter7 scene
Let’s look at each of these layout groups individually and explore their various properties.
具有水平布局组组件的 UI 对象的所有子对象将自动并排放置。如果您允许水平布局组调整子项的大小,它们将被定位和缩放,以便它们完全在父对象的 Rect Transform 的范围内。填充属性可以是但是,如果您希望它们超出父级Rect Transform 的边界,则可以进行调整。
All the children of a UI object with a Horizontal Layout Group component will be automatically placed side by side. If you allow the Horizontal Layout Group to resize the children, they will be positioned and scaled so that they are fully within the bounds of the parent object’s Rect Transform. Padding properties can be adjusted, however, if you’d like them to go outside the bounds of the parent’s Rect Transform.
子项在层次结构中出现的顺序决定了它们由水平布局组布局的顺序。子项将从左到右布局。层次结构中最顶层的子项将放置在最左侧的位置,层次结构中最底层的子项将放置在最右侧的位置:
The order in which the children appear in the Hierarchy determines the order in which they will be laid out by the Horizontal Layout Group. The children will be laid out from left to right. The topmost child in the Hierarchy will be placed in the leftmost position, and the bottommost child in the Hierarchy will be placed in the rightmost position:
图 7.4:第 7 章场景中的水平布局组示例 1
Figure 7.4: Horizontal Layout Groups Example 1 in the Chapter7 scene
要将水平布局组组件添加到 UI 对象,请在对象的检查器中选择添加组件|布局|水平布局组。如果单击Padding属性旁边的箭头,您应该会看到以下内容:
To add a Horizontal Layout Group component to a UI object, select Add Component | Layout | Horizontal Layout Group from within the object’s Inspector. If you click on the arrow next to the Padding property, you should see the following:
图 7.5:水平布局组组件
Figure 7.5: The Horizontal Layout Group component
Let’s explore each of the properties of the Horizontal Layout Group component further.
Padding属性表示父对象的 Rect Transform 边缘周围的填充。正数将使子对象向内移动,负数将使子对象向外移动。
The Padding property represents the padding around the edges of the parent object’s Rect Transform. Positive numbers will move the child objects inward, and negative numbers will move the child objects outward.
Figure 7.6: Horizontal Layout Groups Example 2 in the Chapter7 scene
例如,上图显示了三个面板,它们应用了不同的填充值。第一个面板有没有填充,第二个面板的四个边都有正填充,第三个面板的左侧、右侧和底部都有正填充,但顶部有负填充。
As an example, the previous screenshot shows three Panels with various padding values applied. The first Panel has no padding, the second Panel has positive padding on all four sides, and the third Panel has positive padding on the left, right, and bottom but negative padding on the top.
Spacing属性决定子对象之间的水平间距。如果使用Child Force Expand属性而不使用Control Child Size属性,则这可能会被覆盖,并且子对象可能具有更大的间距g.
The Spacing property determines the horizontal spacing between the child objects. This may be overridden if you use the Child Force Expand property without the Control Child Size property, and the children may have larger spacing.
子对齐属性决定子组将在其中对齐。此属性有九个选项,如下所示:
The Child Alignment property determines where the group of children will be aligned. There are nine options for this property, as shown here:
图 7.7:水平布局组的子对齐选项
Figure 7.7: The Child Alignment options of the Horizontal Layout Group
例如,下图显示了三个覆盖整个屏幕的重叠面板。这些父面板的矩形变换区域由所选的矩形变换表示。第一个面板具有左上子对齐。其子项由白色方块表示。第二个面板具有中间中心子对齐,其子项为用灰色方块表示。第三个面板具有右下子对齐,其子项用黑色方块表示:
As an example, the following diagram shows three overlapping Panels that fill the screen. The Rect Transform area for these parent Panels is represented by the selected Rect Transform. The first Panel has an Upper Left Child Alignment. Its children are represented by the white squares. The second Panel has a Middle Center Child Alignment, and its children are represented by gray squares. The third Panel has a Lower Right Child Alignment, and its children are represented by black squares:
图 7.8:第 7 章场景中的水平布局组示例 3
Figure 7.8: Horizontal Layout Group Example 3 in the Chapter7 scene
需要注意的是,子元素对齐属性仅在子元素(以及间距)不完全填充 Rect Transform,如上图所示是。
It is important to note that the Child Alignment property only shows an effect if the children (along with spacing) don’t completely fill in the Rect Transform, as shown in the preceding diagram.
反向排列属性是一个切换按钮。选择该切换按钮将导致元素按与层次结构中显示的顺序相反的顺序排列。
The Reverse Arrangement property is a toggle. Selecting the toggle will cause the elements to arrange in the reverse order than they appear in the Hierarchy.
控制子尺寸选项允许自动布局会覆盖子对象的当前宽度或高度。如果您选中这些复选框而未选中相应的“子强制扩展”复选框,则子对象将不再可见(除非子对象具有指定了首选宽度的布局元素组件)。
The Control Child Size options allow the automatic layout to override the current Width or Height of the child objects. If you select these checkboxes without selecting the corresponding Child Force Expand checkboxes, your child objects will no longer be visible (unless the children have Layout Element components with Preferred Width specified).
如果不设置此属性,则子项可能会在父项的 Rect Transform 之外绘制 - 也就是说,如果存在太多子项。
If you do not set this property, it is possible that the children will draw outside of the parent’s Rect Transform – that is, if too many children exist.
笔记
Note
此属性会更改子对象的 Rect Transforms 的宽度和高度属性。因此,如果您选择它然后取消选择它,子对象将不会恢复到其原始大小。您必须使用编辑|撤消( Ctrl + Z ) 或通过其 Rect Transform 组件手动重置子对象的大小。
This property changes the width and height property of the child objects’ Rect Transforms. So, if you select and then deselect it, the children will not go back to their original sizes. You will have to either use Edit | Undo (Ctrl + Z) or manually reset the size of the children via their Rect Transform components.
由于此属性取决于Child Force Expand属性,因此下一节将展示Control Child Size属性的示例化。
Since this property depends on the Child Force Expand property, examples of the Control Child Size property are presented in the next section.
子级强制扩展属性将导致子项填充可用空间。如果未选择相应的控件子项大小,则此属性将移动子项,以便它们及其间距填充空间。这可能会覆盖Spacing属性。如果选择了相应的控件子项大小,它将在选定方向上拉伸子项,以便它们及其间距完全填充空间。这将保持Spacing属性。
The Child Force Expand property will cause the children to fill the available space. If the corresponding Control Child Size is not selected, this property will shift the children so that they and their spacing fill the space. This may override the Spacing property. If the corresponding Control Child Size is selected, it will stretch the children in the selected direction so that they and their spacing completely fill the space. This will maintain the Spacing property.
在以下屏幕截图中,所有三个面板都有一个水平布局组组件,该组件具有中左 子对齐方式,并且选择了不同的子控件大小和子强制扩展组合。顶部面板仅选择了子强制扩展宽度,中间面板选择了控制子大小宽度和子强制扩展宽度,最后一个面板同时选择了控制子大小属性和子强制扩展 属性:
In the following screenshot, all three Panels have a Horizontal Layout Group component with a Middle Left Child Alignment and different combinations of Child Control Size and Child Force Expand selected. The top Panel has only Child Force Expand Width selected, the middle Panel has Control Child Size Width and Child Force Expand Width selected, and the last Panel has both Control Child Size properties selected and both Child Force Expand properties selected:
图 7.9:第 7 章场景中的水平布局组示例 4
Figure 7.9: Horizontal Layout Group Example 4 in the Chapter7 scene
Next, let’s look at the Use Child Scale properties.
使用子比例的属性包括仅在较新版本的 Unity 中可用。选中此属性将告诉布局组在自动布局时是否应考虑子项的比例。
The Use Child Scale properties are only available in recent versions of Unity. Checking this property will tell the Layout Group whether it should consider the scale of the children when automating the layout.
垂直布局组组件的工作方式与水平布局组非常相似,并且具有相同的属性,但具有垂直布局组组件的 UI 对象的子对象将自动放在每个上面而不是并排放置。
The Vertical Layout Group component works very similarly to the Horizontal Layout Group and has all the same properties, except children of a UI object with a Vertical Layout Group component will be automatically placed on top of each other, rather than side by side.
与水平布局组一样,子项在层次结构中的显示顺序决定了垂直布局组的布局顺序。子项将按照其在层次结构中的显示顺序从上到下进行布局:
As with the Horizontal Layout Group, the order in which the children appear in the Hierarchy determines the order in which they will be laid out by the Vertical Layout Group. The children will be laid out from top to bottom in the same order in which they appear in the Hierarchy:
图 7.10:第 7 章场景中的垂直布局组示例
Figure 7.10: Vertical Layout Group Example in the Chapter7 scene
添加垂直布局组将组件添加到 UI 对象,请在对象的检查器中选择添加组件|布局|垂直布局组。如果单击Padding属性旁边的箭头,您应该会看到以下内容:
To add a Vertical Layout Group component to a UI object, select Add Component | Layout | Vertical Layout Group from within the object’s Inspector. If you click on the arrow next to the Padding property, you should see the following:
图 7.11:垂直布局组组件
Figure 7.11: The Vertical Layout Group component
由于垂直布局组组件与水平布局组组件相同,我们不会进一步探讨每个属性。有关每个属性的解释,请参阅水平布局组部分。
Since the properties of the Vertical Layout Group component are identical to those of the Horizontal Layout Group, we won’t explore each of the properties further. For an explanation of each of the properties, refer to the Horizontal Layout Group section.
网格布局组组件允许您以网格布局的形式按列和行组织子对象。它工作原理类似于水平和垂直布局但组还具有一些可以操作的属性。
The Grid Layout Group component allows you to organize child objects in columns and rows in (you guessed it) a grid layout. It works similarly to Horizontal and Vertical Layout Groups but has a few more properties that can be manipulated.
要将网格布局组组件添加到 UI 对象,请在对象的检查器中选择添加组件|布局|网格布局组。如果单击Padding属性旁边的箭头,您应该会看到以下内容:
To add a Grid Layout Group component to a UI object, select Add Component | Layout | Grid Layout Group from within the object’s Inspector. If you click on the arrow next to the Padding property, you should see the following:
图 7.12:网格布局组组件
Figure 7.12: The Grid Layout Group component
网格布局组的一些属性与其他两个布局组相同,但让我们进一步了解一下仔细查看网格布局组独有的属性成分。
A few of the properties of the Grid Layout Group are the same as the other two Layout Groups, but let’s look more closely at the properties unique to the Grid Layout Group component.
与水平和垂直布局组不同,它们通过其 Rect Transform 组件或通过缩放它们以适合父级的 Rect Transform 来确定子级的大小,而网格布局组要求您指定子对象的宽度和高度。您可以通过设置Cell Size属性的X和Y属性来实现这一点。这将自动将指定的X和Y大小分别应用于每个子对象的Rect Transform 的Width和Height属性。
Unlike the Horizontal and Vertical Layout Groups, which determine the size of the children either by their Rect Transform component or by scaling them to fit inside the parent’s Rect Transform, the Grid Layout Group requires you to specify the width and height of the child objects. You accomplish this by setting the X and Y properties of the Cell Size property. This will automatically apply the specified X and Y sizes to each of the children’s Width and Height properties of their Rect Transform, respectively.
由于Cell Size属性以及缺少Control Child size 属性,子元素不能保证适合父元素的 Rect Transform。如果子元素过多,它们可能会被绘制在父元素的 Rect Transform 之外。因此,如果您有一个动态填充的网格,该网格在整个游戏过程中可能会发生变化,并且您希望网格始终适合特定区域,那么您必须为溢出情况做好准备。
Due to the Cell Size property and the lack of a Control Child size property, the children are not guaranteed to fit within the parent’s Rect Transform. If too many children exist, it is possible they will be drawn outside the parent’s Rect Transform. So, if you have a grid filling up dynamically that can change throughout the gameplay and want the grid to always fit within a specific area, you will have to prepare for that overflow scenario.
网格布局组允许您指定X 间距和Y 间距。X间距是水平间距,Y 间距是垂直间距。这些值不会被其他属性选择覆盖,因为它们可以与水平和垂直布局组一起使用你群组。
The Grid Layout Group allows you to specify both an X Spacing and Y Spacing. The X Spacing is the horizontal spacing, and the Y Spacing is the vertical spacing. These values will not be overridden by further property choices as they can be with the Horizontal and Vertical Layout Groups.
起始角属性决定层次结构中第一个子项将被放置的位置。Start Corner属性有四种选择,如下所示:
The Start Corner property determines where the very first child in the Hierarchy will be placed. There are four choices for the Start Corner property, as shown here:
图 7.13:网格布局组组件的起始角选项
Figure 7.13: The Start Corner options of a Grid Layout Group component
起始轴属性决定了所有其他子项相对于第一个子项的位置。有两个选项,如下所示:
The Start Axis property determines where all the other children will be placed relative to the first child. There are two options, as follows:
图 7.14:网格布局组组件的起始轴选项
Figure 7.14: The Start Axis options of a Grid Layout Group component
起始轴属性设置为水平意味着子项将从第一个子项开始以水平方式排列。如果将起始角指定为左选项之一,子项将从左到右排列。如果将起始角指定为右选项之一,子项将从右到左排列。新行填满后,它将继续下一行,并从起始角的同一侧重新开始。如果起始角是上选项之一,行将继续向下排列。如果起始角是下选项之一,行将继续向上排列。
A Start Axis property set to Horizontal means that the children will be laid out, starting with the first child, in a horizontal fashion. If the Start Corner is assigned to one of the Left options, the children will be placed from left to right. If the Start Corner is assigned to one of the Right options, the children will be placed from right to left. Once the new row is filled, it will continue to the next row and will restart on the same side as the Start Corner. If the Start Corner is one of the Upper options, the rows will continue downward. If the Start Corner is one of the Lower options, the rows will continue upward.
以下屏幕截图演示了子项的流动,其中水平起始轴基于不同的起始角选项:
The following screenshot demonstrates the flow of the children, with a Horizontal Start Axis based on the different Start Corner options:
图 7.15:第 7 章场景中的网格布局组示例 1
Figure 7.15: Grid Layout Group Example 1 in the Chapter7 scene
起始轴属性设置为垂直意味着子项将从第一个子项开始布局,并且然后以垂直方式排列。子项是从上到下还是从下到上排列,与此属性设置为水平时相同,取决于起始角的位置。然后,当一列填满时,子项将根据起始角的位置从左到右或从右到左排列。
A Start Axis property set to Vertical means that the children will be laid out starting with the first child, and then in a vertical fashion. Whether the children will be placed from top to bottom or from bottom to top is determined in the same way as it is when this property is set to Horizontal, based on the position of the Start Corner. Then, when a column is filled, the children will be placed from left to right or from right to left, based on the position of the Start Corner.
以下屏幕截图演示了子项的流动,其中垂直起始轴基于不同的起始角选项:
The following screenshot demonstrates the flow of the children, with a Vertical Start Axis based on the different Start Corner options:
Figure 7.16: Grid Layout Group Example 2 in the Chapter7 scene
如您所见,起始角和起始轴选项可以极大地改变了子对象的显示顺序。
As you can see, the Start Corner and Start Axis options can greatly change the order in which your child objects are displayed.
Constraint属性允许你指定网格的行数或列数。有三个选项,如下所示:
The Constraint property allows you to specify the number of rows or columns the grid will have. There are three options, as shown here:
图 7.17:网格布局组组件的约束选项
Figure 7.17: The Constraint options of a Grid Layout Group component
固定列数和固定行数属性允许您分别指定列数或行数。如果您选择其中任一选项,则将出现一个新属性“约束数” 。然后,您可以指定所需的列数或行数。当您选择“固定列数”时,行数将是可变的。当您选择“固定行数”时,列数将是可变的。
The Fixed Column Count and Fixed Row Count properties allow you to specify a number of columns or rows, respectively. If you select either of these options, a new property, Constraint Count, will become available. You then specify how many columns or rows you want. When you select Fixed Column Count, the number of rows will be variable. When you select Fixed Row Count, the number of columns will be variable.
灵活选项会根据所选的单元格大小和起始轴选项自动为您计算行数和列数。它将开始按照定义的模式布局子项,直到所选轴上没有剩余空间。然后它将继续。无论哪个轴在“起始轴”中指定的轴将具有固定数量的子项,而另一个轴将是可变的。因此,例如,如果“起始轴”设置为“水平”,并且三个子项可以在定义的空间内水平放置,则将有三列,而行数将由总共有多少个子项决定。
The Flexible option automatically calculates the number of rows and columns for you, based on the Cell Size and the Start Axis options chosen. It will begin laying out the children in the defined pattern until there is no space left on the chosen axis. It will then continue. Whichever axis is specified in Start Axis will have a fixed amount of children, and the other axis will be variable. So, for example, if Start Axis is set to Horizontal and three children can fit horizontally within the defined space, there will be three columns, and the number of rows will be determined by how many total children there are.
现在我们已经探索了三个自动布局组,让我们看一个组件,它可以让我们改变这些布局组中子元素的大小或定位。
Now that we’ve explored the three Automatic Layout Groups, let’s look at a component that will let us change the way the children within these layout groups will be sized or positioned.
如果使用自动布局调整对象大小,则布局元素组件允许我们指定对象的大小值范围。如果如果父对象尝试调整其尺寸超出这些偏好设置,则布局元素将覆盖从父对象发送的任何尺寸信息。
The Layout Element component allows us to specify a range of size values of an object if it is being sized with an automatic layout. If the parent object tries to size it outside of these preferences, the Layout Element will override any sizing information being sent from the parent object.
要将布局元素组件添加到 UI 对象,请在对象的检查器中选择添加组件|布局|布局元素。布局元素具有以下属性:
To add a Layout Element component to a UI object, select Add Component | Layout | Layout Element from within the object’s Inspector. The Layout Element has the following properties:
图 7.18:布局元素组件
Figure 7.18: The Layout Element component
要使用这些属性,首先要选中它们的复选框以启用它们;复选框将变为可用,以便您可以输入你想要的值:
To use these properties, you first select their checkboxes to enable them; boxes will become available so that you can enter your desired values:
Figure 7.19: Setting the properties of the Layout Element component
让我们回顾一下布局元素组件的各个属性将如何影响它们所添加的元素。
Let’s review how the individual properties of the Layout Element component will affect the elements to which they are added.
忽略布局属性可用于使子对象忽略其父对象的任何自动布局组件。子对象选择此属性后,可以自由移动和调整大小,并且所有其他子项都将被布局,而不考虑被忽略的子项。
The Ignore Layout property can be used to make child objects ignore any automatic layout component of its parent object. A child with this property selected can be moved and resized freely, and all other children will be laid out without regard for the ignored child.
在以下示例中,Panel 有一个水平布局组组件和五个子对象。第一个子对象标记为 1,它有一个布局元素组件,并且已选择“忽略布局” 属性:
In the following example, the Panel has a Horizontal Layout Group component and five child objects. The first child, labeled with a 1, has a Layout Element component with the Ignore Layout property selected:
图 7.20:第 7 章场景中的布局元素示例 1
Figure 7.20: Layout Element Example 1 in the Chapter7 scene
您可以看到,由于为第一个子项选择了“忽略布局”属性,因此可以将其移到父面板,在确定其他子面板的位置和比例时,它会被忽略。它还保持了其原始的 Rect Transform 比例。
You can see that since the Ignore Layout property is selected for the first child; it can be moved around outside of the parent Panel, and it was ignored when the position and scale of the other children were determined. It also maintained its original Rect Transform scale.
如果取消选择“忽略布局”属性,则第一个子项将添加到水平布局组中,并且其他孩子。
If the Ignore Layout property is deselected, the first child will be added to the Horizontal Layout Group with the other children.
布局元素组件有三组属性,可用于指定对象调整大小的方式。这些如果分配的尺寸超出提供的值,则属性将覆盖父对象分配给子对象的尺寸。
The Layout Element component has three sets of properties that can be used to specify the way you want an object to resize. These properties will override the size being assigned to the child by the parent object if the assigned size is outside of the provided values.
笔记
Note
需要注意的是,这些属性不会覆盖网格布局组组件的单元格大小设置。它们对 Gr 中的子项没有影响id布局组。
It is important to note that these properties will not override the Cell Size settings of the Grid Layout Group component. They will have no effect on a child within a Grid Layout Group.
Min Width和Min Height属性是子对象可以达到的最小宽度和高度。如果父对象缩放向下,子项将缩小,直到达到其最小宽度或最小高度。一旦达到,它将不再沿该方向缩放。
The Min Width and Min Height properties are the minimum width and height a child object can achieve. If the parent object is scaled down, the child will scale down until it meets its Min Width or Min Height. Once it does so, it will no longer scale in that direction.
在下图中,Panel 有一个水平布局组组件和五个子对象。第一个子对象标记为 1,它有一个布局元素组件,并且设置了最小宽度和最小高度 属性:
In the following diagram, the Panel has a Horizontal Layout Group component and five child objects. The first child, labeled with a 1, has a Layout Element component with the Min Width and Min Height properties set:
图 7.21:第 7 章场景中的布局元素示例 2
Figure 7.21: Layout Element Example 2 in the Chapter7 scene
您可以看到,父对象的水平布局组在缩小自身时尝试将所有子对象也缩小。其他四个子对象也进行了缩放,但由于第一个子对象设置了最小宽度和最小高度属性,因此它拒绝缩放拥有任何进一步的权利。
You can see that the parent object’s Horizontal Layout Group tried to scale all the children down with it as it scaled down itself. The other four children scaled, but since the first child had a Min Width and Min Height properties set, it refused to scale down any further.
首选宽度和首选高度属性有点令人困惑,因为它们的表现不同,具体取决于您对父布局组的设置。没有官方的最大宽度和最大高度设置,尽管有最小宽度和最小高度设置。但是,首选宽度和首选高度属性可用于指定子对象将达到的最大尺寸,但前提是选择了父布局组的正确设置。
The Preferred Width and Preferred Height properties are a little confusing because they perform differently, depending on the settings you have for the parent’s layout group. There is no official Max Width and Max Height setting, despite there being a Min Width and Min Height setting. The Preferred Width and Preferred Height properties, however, can be used to specify the maximum size the child object will achieve, but only if the correct settings on the parent’s layout group are selected.
下图包含三个带有垂直布局组组件和各种设置的面板。它们的子级在布局元素组件中也有各种首选高度设置:
The following diagram contains three Panels with Vertical Layout Group components and various settings. Their children also have various settings for Preferred Height within a Layout Element component:
图 7.22:第 7 章场景中的布局元素示例 3
Figure 7.22: Layout Element Example 3 in the Chapter7 scene
第一个父面板有一个垂直布局组组件,其中选中了控制子尺寸的宽度和高度,以及子强制扩展的宽度和高度。它的所有子项均未在布局元素组件中设置首选宽度或首选高度。比较其他面板时,第一个面板将作为默认参考。
The first parent Panel has a Vertical Layout Group component with Control Child Size Width and Height selected, as well as Child Force Expand’s Width and Height. None of its children have a Preferred Width or Preferred Height setting within a Layout Element component. The first Panel will act as the default for reference when comparing the others.
第二个父面板具有与第一个相同的属性 - 一个垂直布局组组件,其中控制子尺寸的宽度和高度被选中,以及子强制扩展 宽度和高度。但是,它的第一个子组件在布局元素组件中具有首选高度设置为100 。
The second parent Panel has the same properties as the first – a Vertical Layout Group component with Control Child Size’s Width and Height selected, as well as Child Force Expand Width and Height. However, its first child has a Preferred Height setting of 100 within a Layout Element component.
你可以看到,由于第二个父面板选择了Child Force Expand的高度,第一个子面板的Layout Element组件中的Preferred Height为100,导致第一个子面板比其他四个子项高出整整100 个单位。因此,当在父项上选择“子项强制扩展”属性时,具有“首选高度”的子项将不会使用“首选高度”作为其最大可能高度;它会将该值添加到父项的布局组组件分配的高度。
You can see that because the second parent Panel has Child Force Expand’s Height selected, the Preferred Height of 100 in the Layout Element component of the first child causes the first child to be exactly 100 units taller than the other four children. So, when the Child Force Expand property is selected on the parent, the child with the Preferred Height will not use Preferred Height as its maximum possible height; it will add that value to the height assigned by the parent’s layout group component.
第三个面板有一个垂直布局组组件,其中选择了控制子尺寸 宽度和高度,以及子强制扩展的宽度。它没有像其他两个一样选择子强制扩展的高度。它的所有子项在布局元素组件中都将首选高度设置为100 。
The third Panel has a Vertical Layout Group component with Control Child Size Width and Height selected, as well as Child Force Expand’s Width. It does not have Child Force Expand’s Height selected as the other two do. All of its children have a Preferred Height setting of 100 within a Layout Element component.
如果将第三个面板中的子项与第一个面板(默认)中的子项进行比较,您会发现子项较矮。这是因为它们的首选高度设置为小于垂直布局组组件尝试分配给它们的高度。因此,当取消选择子项强制扩展的Height属性时,子项将以预期的方式使用其首选高度设置 - 使其成为子项应达到的最大尺寸。
If you compare the children in the third Panel to the children in the first Panel (the default), you can see that the children are shorter. This is because their Preferred Height is set to a smaller number than the height that the Vertical Layout Group component attempts to assign to them. So, when the Child Force Expand’s Height property is deselected, the children will use their Preferred Height setting in the expected way – making it the maximum size the children should attain.
因此,如果您希望“首选宽度”或“首选高度”设置作为可达到的最大宽度或高度,则需要取消选择相应的“子强制扩展”属性父对象。
Therefore, if you want the Preferred Width or Preferred Height settings to work as a maximum attainable width or height, you will need to deselect the corresponding Child Force Expand property on the parent object.
Flexible Width和Flexible Height属性表示百分比,其中百分比是子元素相对的大小给其他子项。由于这些值是百分比,因此 0 表示 0%,1 表示100%。
The Flexible Width and Flexible Height properties represent a percentage, where the percentage is the size of the child relative to the other children. Since these values are percentages, a value of 0 would represent 0% and a value of 1 would represent 100%.
与Preferred Width和Preferred Height一样,除非取消选择Child Force Expand属性,否则此设置不会按预期工作。在下面的示例中,两个面板和子面板具有几乎相同的设置。两者之间的唯一区别是顶部父面板选择了Child Force Expand的Width属性,而底部父面板没有。因此,您可以看到,如果在父面板上选择了Child Force Expand的Width,则子面板的Flexible Width设置的值将被忽略:
As with Preferred Width and Preferred Height, this setting doesn’t work as expected unless the Child Force Expand property is deselected. In the following example, the two Panels and children have nearly identical settings. The only difference between the two is that the top parent Panel has Child Force Expand’s Width property selected and the bottom parent Panel does not. So, you can see that the values set for Flexible Width of the children are ignored if Child Force Expand’s Width is selected on the parent:
图 7.23:第 7 章场景中的布局元素示例 4
Figure 7.23: Layout Element Example 4 in the Chapter7 scene
第二个孩子上图的行从左到右具有以下Flexible Width设置: 0、0.5、0.75、1和1.5。您可以看到子项已根据百分比相互缩放。第一个子项不可见,因为它的Flexible Width 为0。
The children in the second row of the preceding figure have the following Flexible Width settings from left to right: 0, 0.5, 0.75, 1, and 1.5. You can see that the children have scaled relative to each other based on the percentages. The first child is not visible because it has a Flexible Width of 0.
布局元素组件本质上允许我们覆盖元素的自动大小和位置。现在,让我们回顾一下允许我们自动覆盖元素的某些组件y 尺寸UI 元素。
The Layout Element component essentially lets us override the automatic size and position of an element. Now, let’s review some components that will allow us to automatically size UI elements.
有两个 fitter 布局组件。这些组件使其所附着的对象的 Rect Transform 适合在指定区域内。
There are two fitter layout components. These components make the Rect Transform of the object on which they are attached fit within a specified area.
Content Size Fitter组件允许你强制父级的尺寸适应其子级的尺寸。此适配可以基于孩子的最小尺寸或首选尺寸。
The Content Size Fitter component allows you to force the size of the parent to fit around the size of its children. This fitting can be based on the minimum or preferred size of the children.
要将Content Size Fitter组件添加到 UI 对象,请在对象的检查器中选择添加组件|布局| Content Size Fitter。Content Size Fitter组件具有以下属性:
To add a Content Size Fitter component to a UI object, select Add Component | Layout | Content Size Fitter from within the object’s Inspector. The Content Size Fitter component has the following properties:
图 7.24:Content Size Fitter 组件
Figure 7.24: The Content Size Fitter component
您可以为水平适配和垂直适配选择以下属性:
You can choose the following properties for the Horizontal Fit and the Vertical Fit:
图 7.25:Content Size Fitter 组件可能的适配选项
Figure 7.25: The possible fit options of the Content Size Fitter component
如果选择了“不受约束”属性, Content Size Fitter将不会沿该轴调整对象的大小。
If the Unconstrained property is selected, Content Size Fitter will not adjust the size of the object along that axis.
如果选择了Min Size属性, Content Size Fitter将根据子元素的最小尺寸调整对象的尺寸。此最小尺寸由子元素的Layout Element组件的Min Width和Min Height属性决定。
If the Min Size property is selected, the Content Size Fitter will adjust the size of the object based on the minimum size of the children. This minimum size is determined by the Min Width and Min Height properties of the Layout Element component of the children.
如果父级具有网格布局组组件,则子级不必具有布局元素组件,此属性才有效。如果为具有网格布局组组件的对象选择了此属性,则父级的 Rect Transform 将根据单元格大小和填充属性拥抱子级,如下图所示:
The children do not have to have a Layout Element component if the parent has a Grid Layout Group component for this property to work. If this property is selected for an object with a Grid Layout Group component, the Rect Transform of the parent will hug the children based on the Cell Size and Padding properties, as shown in the following diagram:
图 7.26:第 7 章场景中的 Content Size Fitter 示例
Figure 7.26: The Content Size Fitter Example in the Chapter7 scene
如果首选大小属性为选中后,Content Size Fitter将根据子项的首选大小调整对象的大小。此首选大小由子项的Layout Element组件的Preferred Width和Preferred Height属性决定。如果对象具有Grid Layout Group组件,则此设置将以完全相同的方式执行我将其视为最小尺寸。
If the Preferred Size property is selected, the Content Size Fitter will adjust the size of the object based on the preferred size of the children. This preferred size is determined by the Preferred Width and Preferred Height properties of the Layout Element component of the children. If the object has a Grid Layout Group component, this setting will perform in the exact same way as Min Size.
纵横比适配组件的工作原理类似于布局元素组件,因为它允许您覆盖发送给它的尺寸限制。它将强制其所连接的 UI 对象根据纵横比调整大小。
The Aspect Ratio Fitter component works similarly to the Layout Element component, as it allows you to override the size constraints being sent to it. It will force the UI object on which it is attached to resize based on an aspect ratio.
要将Aspect Ratio Fitter组件添加到 UI 对象,请在对象的Inspector中选择Add Component | Layout | Aspect Ratio Fitter (Script)。Aspect Ratio Fitter组件具有以下属性:
To add an Aspect Ratio Fitter component to a UI object, select Add Component | Layout | Aspect Ratio Fitter (Script) from within the object’s Inspector. The Aspect Ratio Fitter component has the following properties:
图 7.27:Aspect Ratio Fitter 组件
Figure 7.27: The Aspect Ratio Fitter component
一旦选择了“纵横比”选项,纵横比属性将处于可编辑状态。纵横比属性定义了 Rect Transform 将保持的纵横比。例如,如果您想要纵横比为 4:3,您只需在框中输入 4/3,它就会将其转换为十进制值:
Once you select an Aspect Mode option, the Aspect Ratio property will be editable. The Aspect Ratio property defines the aspect ratio that the Rect Transform will maintain. For example, if you want an aspect ratio of 4:3, you can simply enter 4/3 in the box, and it will convert it to the decimal value:
图 7.28:将分数输入到纵横比拟合器组件中
Figure 7.28: Entering fractions into an Aspect Ratio Fitter Component
您可以为“纵横比模式”属性选择以下属性:
You can choose the following properties for the Aspect Mode property:
图 7.29:Aspect Ratio Fitter 组件的 Aspect Mode 选项
Figure 7.29: The Aspect Mode options for the Aspect Ratio Fitter component
如果选择了None属性,则纵横比适配器将不会调整尺寸以适合纵横比。
If the None property is selected, the Aspect Ratio Fitter will not adjust the size to fit within the Aspect Ratio.
如果选择了“宽度控制高度”属性,则纵横比适配器将根据对象的宽度调整高度的大小。
If the Width Controls Height property is selected, the Aspect Ratio Fitter will adjust the size of the height based on the width of the object.
如果选择了“高度控制宽度”属性,则纵横比适配器将根据对象的高度调整宽度的大小。
If the Height Controls Width property is selected, the Aspect Ratio Fitter will adjust the size of the width based on the height of the object.
如果选择了“适合父级”属性,则“长宽比适配器”将调整对象的大小以适合其父对象,但将保持“长宽比”。这将使子对象保持在父级的范围内。
If the Fit In Parent property is selected, the Aspect Ratio Fitter will adjust the size of the object to fit within its parent object but will maintain the Aspect Ratio. This will make the child object stay within the bounds of the parent.
如果选择了Envelope Parent属性, Aspect Ratio Fitter将调整对象的大小以覆盖其父对象,但将保持Aspect Ratio。这类似于Fit In Parent属性,不同之处在于它不是停留在父级的边界内,而是可以超出父级的边界。界限。
If the Envelope Parent property is selected, the Aspect Ratio Fitter will adjust the size of the object to cover its parent object but will maintain the Aspect Ratio. This is similar to the Fit In Parent property, except that instead of staying within the bounds of the parent, it can go outside the bounds.
如果您尝试将Aspect Ratio Fitter组件添加到具有布局组组件的父级的子级,您将在子级上看到以下消息:
If you try to add an Aspect Ratio Fitter component to a child with a parent that has a layout group component, you’ll see the following message on the child:
图 7.30:Aspect Ratio Fitter 警告消息
Figure 7.30: The Aspect Ratio Fitter warning message
虽然您可以忽略此消息并继续执行此操作,但这样做并不完全符合预期。建议的解决方法是将Aspect Ratio Fitter组件添加到组内子项的子项中。例如,在下图中,将 Panel 添加为Horizontal Layout Group的子项。然后,将带有Aspect Ratio Fitter组件的子项添加到 Panel 中,以便子项可以具有 4:3 的宽高比:
While you can ignore this message and do it anyway, it doesn’t work entirely as expected. The recommended workaround is to add the Aspect Ratio Fitter component to a child of the child within the group. For example, in the following diagram, a Panel was added as a child of the Horizontal Layout Group. Then, a child with an Aspect Ratio Fitter component was added to the Panel so that the child could have the 4:3 Aspect Ratio:
Figure 7.31: Aspect Ratio Fitter Example in the Chapter7 scene
现在我们已经了解了各种自动布局组件的所有属性,让我们看一些如何使用它们的示例!
Now that we’ve looked at all the properties of the various automatic layout components, let’s look at some examples of how to use them!
我们将继续处理第 6 章中创建的场景并使用为其导入的艺术资产。
We’ll continue working on the scene created in Chapter 6 and use the art assets imported for them.
笔记
Note
如果您没有遵循第 6 章中的示例,但想要遵循这些示例,您可以从代码包中下载名为第 07 章- 示例 - Start.unitypackage 的Unity 包。
If you did not follow along with the examples in Chapter 6 but would like to follow along with these, you can download the unity package named Chapter 07 - Examples – Start.unitypackage from the code bundle.
除了已经添加到我们的项目中的艺术作品之外,我们还将使用我根据在https://opengameart.org/content/platformer-pickups-pack找到的免费艺术资产修改过的艺术资产。
In addition to the art already added to our project, we’ll be using art assets that I’ve modified from free art assets found at https://opengameart.org/content/platformer-pickups-pack.
从上一个链接下载的版本提供了许多单独的图像。我本来可以使用这些图像,但出于性能原因,最好尽可能使用精灵表。因此,您可以在代码包中找到标记为foodSpriteSheet.png 的精灵表。为了将所有图像组合成精灵表,我使用了 Texture Packer 程序,该程序可在https://www.codeandweb.com/texturepacker找到。
The download from the previous link provides many individual images. I could have used those, but for performance reasons, it is best to use sprite sheets whenever possible. So, you can find the sprite sheet labeled foodSpriteSheet.png in the code bundle. To combine all the images into a sprite sheet, I used the program Texture Packer, which can be found at https://www.codeandweb.com/texturepacker.
Before you begin with the following examples, complete the following steps:
图 7.32:需要移除的空精灵
Figure 7.32: An empty sprite that needs removing
图 7.33:当前项目中的所有精灵
Figure 7.33: All sprites currently in the project
现在您已经复制了场景并导入了艺术作品,让我们看看如何应用一些自动布局。
Now that you have the scene duplicated and the art imported, let’s look at applying some automatic layouts.
本章中我们将介绍的第一个示例是屏幕右下角的 HUD 选择菜单使用水平布局组组件。完成后,它将如下图所示:
The first example we will cover in this chapter is a HUD selection menu in the lower-right corner of the screen that uses the Horizontal Layout Group component. When we are done, it will look like the following figure:
图 7.34:我们将在此示例中构建的 HUD 选择菜单
Figure 7.34: The HUD selection menu we will build in this example
要创建如上图所示的 HUD 组,请完成以下步骤:
To create the HUD group shown in the preceding screenshot, complete the following steps:
图 7.35:层次结构中的面板
Figure 7.35: The Panels in the Hierarchy
图 7.36:右下方面板的矩形变换
图 7.37:生成的面板
Figure 7.36: The Rect Transform of the Bottom Right Panel
You should see the following in your Game view:
Figure 7.37: The resulting Panel
图 7.38:面板的图像元件属性
图 7.39:生成的面板
Figure 7.38: The Image component properties of the Panel
You should now see the following in your Game view:
Figure 7.39: The resulting Panel
图 7.40:UI 元素的层次结构
Figure 7.40: The Hierarchy of UI elements
图 7.41:食物元素的图像组件
你应该看到类似这样的内容:
图 7.42:结果面板上有橙色
如果您的橙子及其托架与我的不同,请不要担心。当我们开始添加更多子项并调整水平组布局时,所有内容都会弹到正确的位置。
Figure 7.41: The Image component of the Food element
You should see something that looks like this:
Figure 7.42: The resulting Panel with an orange
If your orange and its holder are in a different place than mine, don’t worry. When we start adding more children and adjusting the Horizontal Group Layout, everything should pop into its proper place.
图 7.43:食物 UI 图像的 Rect Transform 和 Image 组件
Figure 7.43: The Rect Transform and Image components of the Food UI Image
图 7.44:UI 元素的层次结构
您应该在游戏视图中看到以下内容:
图 7.45: 生成的面板上有五个橙子
Figure 7.44: The Hierarchy of UI elements
You should see the following in the Game view:
Figure 7.45: The resulting Panel with five oranges
图 7.46:重命名元素的层次结构
Figure 7.46: The Hierarchy of renamed elements
图 7.47:右下面板的水平布局组组件
图 7.48:生成的橙子面板
通过调整Padding属性,我们将对象组移出父面板的边缘。我们通过更改Spacing属性将它们隔开,并通过启用控制子尺寸宽度和高度来确保调整项目容器图像的尺寸以适合父面板。
Figure 7.47: The Horizontal Layout Group component of the Bottom Right Panel
You will now be able to see the following:
Figure 7.48: The resulting Panel of oranges
By adjusting the Padding properties, we brought the group of objects off the edge of the parent Panel. We spaced them apart by changing the Spacing property, and we ensured that the sizes of the Item Holder images were adjusted to fit onto the parent Panel by enabling Control Child Size Width and Height.
图 7.49:结果显示各种水果
Figure 7.49: The resulting Panel of a variety of fruit
从这个例子中你可以看到,水平布局组组件和(类似的)垂直布局组组件设置起来并不太难,并且对于创建组织良好的zed 列表。
As you can see from this example, Horizontal Layout Group components and (similarly) Vertical Layout Group components aren’t too difficult to set up and are extremely useful for creating well-organized lists.
本章我们将介绍的最后一个例子是使用网格布局组组件和内容适配组件创建网格库存系统。我们将在后面的章节中继续研究此面板:
The last example we’ll cover in this chapter is the creation of a gridded inventory system using a Grid Layout Group component and the Content Fitter component. We’ll continue to work on this Panel in later chapters:
图 7.50:我们将在此示例中构建的电网清单
Figure 7.50: The grid inventory we will build in this example
要创建上图所示的网格库存系统,请完成以下步骤:
To create the gridded inventory system shown in the preceding screenshot, complete the following steps:
图 7.51:复制和重命名后生成的层次结构
Figure 7.51: The resulting Hierarchy after duplicating and renaming
笔记
Note
记得我们在第 6 章中向暂停面板添加了一个 Canvas Group 组件。通过复制它来创建库存面板,库存面板也有一个Canvas Group组件。此组件将允许我们轻松隐藏和显示两个面板,我们将在下一章中执行此操作。
Remember that we added a Canvas Group component to the Pause Panel in Chapter 6. By duplicating it to create the Inventory Panel, the Inventory Panel has a Canvas Group component as well. This component will allow us to easily hide and show the two Panels, which we will do in the next chapter.
图 7.52:库存面板的 Rect Transform 和 Image 组件
你现在应该在游戏视图中看到以下内容:
图 7.53:生成的库存面板
Figure 7.52: The Rect Transform and Image components of the Inventory Panel
You should now see the following in your Game view:
Figure 7.53: The resulting Inventory Panel
图 7.54:项目的层次结构
Figure 7.54: The Hierarchy of items
图 7.55:项目的层次结构
Figure 7.55: The Hierarchy of items
图 7.56:项目的层次结构
现在你应该在游戏视图中看到类似于图 7.57的内容。根据你由于复制或执行顺序不同,水果的顺序可能会略有不同。不过,这没关系!
图 7.57:水果网格
Figure 7.56: The Hierarchy of items
You should now see something similar to Figure 7.57 in your Game view. Depending on what you duplicated or the order in which you did it, the fruit may be in a slightly different order. That’s fine, though!
Figure 7.57: The grid of fruit
图 7.58:库存持有者的网格布局组组件
Figure 7.58: The Grid Layout Group component of Inventory Holder
图 7.59:库存支架的网格布局组组件
我们使用Spacing属性在每个单元格之间留出间距,使用Child Alignment Middle Center属性将子元素置于对象中心,并给出它是使用固定列数约束并将约束数 设置为3 的3x3 布局。
Figure 7.59: The Grid Layout Group component of Inventory Holder
We put spacing between each of the cells using the Spacing property, centered the children in the object using the Child Alignment Middle Center property, and gave it a 3x3 layout using Fixed Column Count Constraint and by setting Constraint Count to 3.
图 7.60:Inventory Holder 的 Content Size Fitter 组件
你现在应该在游戏视图中看到以下内容:
图 7.61: 水果的拟合网格
虽然有点难以看清,但库存容器图像现在紧贴在物品容器图像网格周围。不过,我们需要一点填充。
Figure 7.60: The Content Size Fitter component of Inventory Holder
You should now see the following in your Game view:
Figure 7.61: The fitted grid of fruit
It’s a little hard to see, but the Image of Inventory Holder now fits snuggly around the grid of Item Holder Images. We want a bit of padding, though.
图 7.62:水果的填充网格
Figure 7.62: The padded grid of fruit
图 7.63:各种食物的网格
就是这样。您现在应该有一个布局完美的库存网格。
通过设置Grid Layout Group组件以及Content Size Fitter,我们现在可以更改网格中的项目数量,并且Inventory Holder将自动调整大小以适合所有项目,如下所示:
图 7.64:各种食物的小网格
这确实有效好吧,直到我们尝试向库存中添加更多物品。你会发现,一旦我们有 10 个物品,一切看起来都很糟糕:
图 7.65:各种食物的扩展网格
我们可以采取一些措施来处理这个问题,包括更改单元格大小以及使用蒙版和Scroll Rect。我们将在后面的章节中讨论如何进行这些更改。不过现在,只需将库存保持在 9 件即可,这样一切看起来都很好。
完成最后两章的所有示例后,您应该具有以下内容:
图 7.66:第 6 章和第 7 章所有示例的结果
Figure 7.63: The grid of various foods
That’s it. You should now have a perfectly laid out inventory grid.
With the Grid Layout Group component set up along with our Content Size Fitter, we can now change the number of items in the grid, and the Inventory Holder will automatically resize to fit all the items, as you can see here:
Figure 7.64: The smaller grid of various foods
This actually works really well, until we try to add more items to the inventory. You’ll see that once we have 10 items, everything looks pretty bad:
Figure 7.65: The expanded grid of various foods
There are a few things we can do to handle this, including changing the cell size and using a mask along with a Scroll Rect. We’ll discuss how to make those changes in a later chapter. For now, though, just leave your inventory at nine items so that everything looks nice.
After completing all the examples in the last two chapters, you should have the following:
Figure 7.66: The result of all examples from Chapters 6 and 7
这是为了添加自动布局到我们的场景。我们将在将来的章节中继续改进它之语。
And that’s in for adding automatic layouts to our scene. We’ll continue to improve upon it in future chapters.
现在,我们知道了各种布局 UI 元素的技术。本章和上一章中介绍的信息提供了足够的工具来创建几乎任何你能想到的 UI 布局。
Now, we know all sorts of techniques to lay out our UI elements. The information covered in this chapter, and the last one, has provided enough tools to create almost any UI layout that you can imagine.
本章讨论的自动布局不仅仅在您想要手动添加 UI 项时有用,就像我们在本章中所做的那样。如果您想根据特定条件动态创建和添加 UI 项,这些自动布局尤其有用。
The automatic layouts discussed in this chapter aren’t just helpful when you want to manually add UI items, as we did in this chapter. These automatic layouts are particularly helpful if you want to dynamically create and add your UI items based on specific conditions.
在下一章中,我们将学习如何通过代码访问 UI 组件以及如何使用事件系统让玩家与UI 对象交互。
In the next chapter, we will learn how to access UI components via code and how to use the Event System to allow the player to interact with the UI objects.
Unity UI 系统的一个关键特性是能够轻松编程 UI 元素如何接收来自玩家的交互通过事件。事件系统是一个强大的系统,允许您创建和管理事件。
One of the key features of the Unity UI system is the ability to easily program how the UI elements receive interactions from the player via events. The Event System is a robust system that allows you to create and manage events.
一旦您学会如何利用事件系统,您将能够创建可交互的 UI 以及响应游戏中事件的 UI。
Once you learn how to take advantage of the Event System, you will be able to create interactable UI as well as UI that responds to events in your game.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
您可以在此处找到本章节的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2008
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2008
所有 UI 元素可以像其他 GameObject 一样在代码中访问和操作。要在代码中访问 UI 元素,必须包含UnityEngine.UI命名空间和正确的变量类型。让我们看看UnityEngine.UI命名空间。
All the UI elements can be accessed and manipulated in code like other GameObjects. To access a UI element in code, you must include the UnityEngine.UI namespace and the correct variable type. Let’s look at the UnityEngine.UI namespace.
命名空间类的集合。当你在类中包含命名空间时,你表明你想要访问所有类中的变量和方法(函数)。使用 using关键字可在脚本顶部访问命名空间。
A namespace is a collection of classes. When you include a namespace in your class, you are stating that you want to access all the variables and methods (functions) in your class. Namespaces are accessed at the top of a script with the using keyword.
默认情况下,所有新的 C# 脚本都包含System.Collections、System.Collections.Generic和UnityEngine命名空间。要通过代码访问 UI 元素的属性,必须首先使用UnityEngine.UI命名空间。
By default, all new C# scripts include the System.Collections, System.Collections.Generic and UnityEngine namespaces. To access the properties of UI elements via code, you must first use the UnityEngine.UI namespace.
因此,您需要在 C# 脚本的顶部包含以下行来表示您想要使用UnityEngine.UI命名空间:
Therefore, at the top of your C# script, you will need to include the following line to signify that you want to use the UnityEngine.UI namespace:
使用 UnityEngine.UI;
using UnityEngine.UI;
如果不使用命名空间,任何与 UI 元素相关的变量类型都会在代码编辑器中显示为红色,并且会出现编译器错误。一旦包含命名空间,变量类型就会变为蓝色文本,表示它是可用的变量类型,并且编译器错误会消失啊。
Without using the namespace, any variable type related to UI elements will be colored red in your code editor, and you will be given a compiler error. Once you include the namespace, the variable type will change to the blue-colored text, signifying that it is an available variable type, and the compiler error will disappear.
每种变量类型都是UnityEngine.UI命名空间内的类。因此,这些变量类型中的每一种都有自己的一组可以访问的变量和函数。我们将在以后的章节中更深入地讨论每种变量类型,但现在,我们只看一下在代码中访问 UI 元素属性的标准模板。
Each variable type is a class within the UnityEngine.UI namespace. Therefore, each of these variable types, in turn, has its own set of variables and functions that can be accessed. We’ll discuss each variable type more thoroughly in future sections and chapters, but for now, let’s just look at the standard template for accessing a property of a UI element in code.
您可以在源文件中找到一个名为Chapter 08 .unitypackage 的Unity 包。导入它将引入一个名为Chapter8.unity的场景和各种代码文件。从包中导入项目并打开场景。在Chapter8场景中,您将看到一个名为UI Variables Example 的UI 图像。它没有分配精灵,显示为白色方块。以下脚本AddSprite.cs已附加到UI 图像:
You can find within the source files a Unity package named Chapter 08.unitypackage. Importing it will bring in a scene named Chapter8.unity and various code files. Import the items from the package and open the scene. In the Chapter8 scene, you will see a UI Image named UI Variables Example. It does not have a sprite assigned to it and appears as a white square. The following script, AddSprite.cs, is attached to the UI Image:
使用System.Collections;
使用 System.Collections.Generic;
使用 UnityEngine;
使用 UnityEngine.UI;
公共类 AddSprite:MonoBehaviour {
图像图像;
公共 Sprite theSprite;
无效唤醒(){
theImage = GetComponent<Image>();
}
无效开始(){
图像.精灵 = 精灵;
图像.preserveAspect = true;
}
}using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class AddSprite : MonoBehaviour {
Image theImage;
public Sprite theSprite;
void Awake(){
theImage = GetComponent<Image>();
}
void Start () {
theImage.sprite = theSprite;
theImage.preserveAspect = true;
}
} 上述代码中突出显示了特定于 UI 的代码片段。请注意,UnityEngine.UI命名空间包含在类的顶部。
The UI-specific pieces of code are highlighted in the preceding code. Note that the UnityEngine.UI namespace is included at the top of the class.
类中定义了两个公共变量:theImage是Image类型,theSprite是Sprite类型。theImage变量引用场景中的 UI Image,theSprite变量引用将成为UI Image源图像的精灵。
There are two public variables defined in the class: theImage, which is an Image type, and theSprite, which is a Sprite type. The theImage variable is referencing the UI Image in the scene and the theSprite variable is referencing the sprite that will become the source image of the UI Image.
Image变量类型位于UnityEngine.UI命名空间内,代表 UI Image GameObject。Sprite变量类型不是 UI 元素,包含在UnityEngine命名空间中。
The Image variable type is within the UnityEngine.UI namespace and represents UI Image GameObject. The Sprite variable type is not a UI element and is included in the UnityEngine namespace.
在Start()函数中,通过在变量名称后键入句点然后键入属性来引用theImage上的Image组件的属性。您可以访问以此方式访问 UI 元素对应组件中出现的任何属性。您还可以通过这种方式访问组件中未列出的属性。
Within the Start() function, the properties of the Image component on theImage are referenced by typing a period and then the property after the variable name. You can access any property that appears in a UI element’s corresponding component in this way. You can also access properties that are not listed in the component this way.
附加到UI Variables Example (Image) 的AddSprite脚本出现在检查器中,如以下屏幕截图所示:
The AddSprite script attached to UI Variables Example (Image) appears in the inspector, as shown in the following screenshot:
图 8.1:AddSprite 脚本及其属性
Figure 8.1: The AddSprite script and its properties
现在,播放场景时,精灵将从空白的白色方块变为香蕉图像,且其纵横比保持不变。
Now, when the scene is played, the sprite will change from a blank white square to an image of a banana with its aspect ratio preserved.
让我们探索事件系统,它将允许我们与我们进行交互r 用户界面。
Let’s explore the Event System, which will allow us to interact with our UI.
在第 6 章中,我们了解到,当第一个 Canvas 添加到场景时,一个名为EventSystem的游戏对象会自动添加到层次结构中。事件系统允许您轻松接收玩家交互并通过事件将这些交互发送给场景中的对象。请注意,我说的是场景中的对象,而不是UI 对象。事件系统还允许您将事件发送给非 UI项目!
In Chapter 6, we learned that when the first Canvas is added to a scene, a GameObject named EventSystem is automatically added to the Hierarchy. The Event System allows you to easily receive player interactions and send those interactions to objects in your scene through events. Note that I said, objects in your scene and not UI objects. The Event System allows you to send events to non-UI items, too!
在我们继续之前,我想说明一下我对EventSystem(一个词)和 Event System(两个词)的使用,因为我将在这两个词之间来回切换。我想让你知道我是故意这样做的,并不是随便决定有时我讨厌空格键。
Before we proceed, I’d like to note my use of EventSystem (one word) and Event System (two words), because I will be switching back and forth between the two. I want you to know that I am doing it deliberately and am not just randomly deciding that sometimes I hate the spacebar.
我将使用EventSystem(一个词)来引用场景层次结构中出现的实际 GameObject,并使用 Event System(两个词)来引用处理事件的系统。
I will use EventSystem (one word) to reference the actual GameObject that appears in the Hierarchy of your scene and Event System (two words) to reference the system that handles events.
事件系统除了向对象发送事件之外,它还为您做了很多事情。它还跟踪当前选定的游戏对象、输入模块和光线投射。
The Event System does quite a few things for you other than just sending events to objects. It also keeps track of the currently selected GameObject, the Input Modules, and Raycasting.
EventSystem GameObject 默认初始化三个组件:Transform、Event System Manager 和Standalone Input Module,如以下屏幕截图所示:
The EventSystem GameObject initializes, by default, with three components: the Transform, Event System Manager, and Standalone Input Module, as shown in the following screenshot:
图 8.2:EventSystem GameObject 及其组件
Figure 8.2: The EventSystem GameObject and its components
由于EventSystem是一个 GameObject,因此它物理上存在于场景中(即使它没有可渲染的组件使其可见),因此像所有其他 GameObject 一样具有Transform组件。您现在应该熟悉Transform组件,因此我们不会进一步讨论它。但是,其他两个组件确实值得进一步讨论。现在让我们更仔细地看看事件系统组件。我们还将在本章的输入模块部分讨论独立输入模块组件。
Since EventSystem is a GameObject, it physically exists within the scene (even though it has no renderable component making it visible) and therefore has a Transform component like all other GameObjects. You should be familiar with the Transform component by now, so we won’t discuss it further. However, the other two components do merit further discussion. Let’s look at the Event System component more closely now. We’ll also discuss the Standalone Input Module component in the Input Modules section of this chapter.
场景中不能有多个EventSystem GameObject。如果您尝试通过+ | UI | Event System在场景中添加新对象,则不会添加新对象,并且会为您选择场景中的当前对象。
You cannot have more than that one EventSystem GameObject in your scene. If you try to add a new one in the scene via + | UI | Event System, a new one will not be added, and the one currently in the scene will be selected for you.
笔记
Note
如果您设法在场景中添加第二个事件系统(例如使用Ctrl + D复制现有的事件系统),您将在控制台上看到一条警告消息。
If you manage to add a second EventSystem to your scene (by perhaps using Ctrl + D to duplicate the existing one), you will see a warning message on your Console.
如果场景中有多个EventSystem GameObject,则只有第一个添加的 EventSystem 才会真正起作用。任何其他EventSystem都将不起作用。
If you have more than one EventSystem GameObject in your scene, only the first one added will actually do anything. Any additional EventSystems will be non-functional.
Let’s look at the Event System Manager next.
事件系统经理是实际执行所有跟踪和管理各种事件系统元素的组件。
Event System Manager is the component that actually does all the tracking and managing of the various Event System elements.
如果您想在不使用 UI 的情况下使用事件系统,则不会自动为您创建事件系统游戏对象。 您可以通过在对象的检查器上选择添加组件|事件|事件系统,将事件系统管理器添加到游戏对象。 让我们讨论一下事件系统管理器下的属性阿纳格。
If you want to work with the Event System without using UI, the EventSystem GameObject will not be automatically created for you. You can add an Event System Manager to a GameObject by selecting Add Component | Event | Event System on the object’s Inspector. Let’s talk about the properties under the Event System Manager.
你知道当你启动游戏时,Start Game按钮会为你突出显示,这样按Enter键就可以开始游戏,而不必使用鼠标?这就是First Selected属性为你做的。它会为你选择场景中的一个UI元素启动时自动运行。
You know when you start up a game and the Start Game button is highlighted for you so that hitting Enter will start the game without you having to use your mouse? That’s what the First Selected property does for you. It selects a UI element in the scene for you automatically when it starts up.
您可以将任何难以处理的 UI 元素拖放到此插槽中,使其成为场景中第一个选定的 UI 项目。这对于不使用鼠标或触摸屏而仅依赖游戏手柄、操纵杆或键盘。
You can drag and drop any intractable UI element into this slot to make it the first selected UI item in your scene. This is particularly helpful for games that do not use a mouse or touchscreen but rely solely on a gamepad, joystick, or keyboard.
可以打开或关闭“发送导航事件”属性。启用此属性后,您可以可以通过游戏手柄、操纵杆或键盘在 UI 元素之间导航。可以使用以下导航事件:
The Send Navigation Events property can be toggled on and off. When this property is enabled, you can navigate between UI elements via a gamepad, joystick, or keyboard. The following navigation events can be used:
Drag Threshold属性表示 UI 对象可以移动的像素数才会被视为被拖拽。人们的手不是完全稳定的,所以当他们试图点击或轻触 UI 项目时,他们的鼠标或手指可能会稍微移动。这个拖拽阈值允许玩家在他们选择的项目被拖拽之前稍微移动他们的输入(或者如果你把这个数字设得很高,可以移动很多),而不是韩点击了。
The Drag Threshold property represents the number of pixels a UI object can be moved before it is considered being dragged. People don’t have perfectly steady hands, so when they are trying to click or tap a UI item, their mouse or finger may move slightly. This Drag Threshold allows the player to move their input slightly (or a lot if you make this number high) before the item they are selecting is dragged rather than clicked.
前我们讨论事件系统管理器的下一个组件,我想讨论输入管理器。输入管理器是您在游戏中定义轴的地方,方法是将它们分配给鼠标、键盘或操纵杆(游戏手柄)上的按钮。这还允许您在编码时使用轴名称来轻松引用要在操作中执行的所有输入。
Before we discuss the next component of the Event System Manager, I want to discuss the Input Manager. The Input Manager is where you define the axes in your game by assigning them to the buttons on your mouse, keyboard, or joystick (gamepad). This also allows you to use the axis name when coding to easily reference all inputs that you want to perform in an action.
笔记
Note
请记住,正如我们在第 5 章中讨论的那样,实际上有两个系统可让您处理游戏中的输入:输入管理器和新输入系统。本章将重点介绍输入管理器。我们将在以后的章节中讨论新输入系统。
Remember, as we discussed in Chapter 5, there are actually two systems that will allow you to handle input in your game: the Input Manager and the new Input System. This chapter will focus on the Input Manager. We will discuss the new Input System in a future chapter.
要打开输入管理器,请选择编辑|项目设置|输入管理器。
To open the Input Manager, select Edit | Project Settings | Input Manager.
If you select the arrow next to Axes, you will see the default list of axes:
图 8.3:输入管理器及其所有预定义轴
Figure 8.3: The Input Manager and all its pre-defined axes
默认情况下,总共有 30 个轴。更改“大小”旁边的数字将为您提供更多或更少的轴。展开单个轴将显示以下内容:
There are 30 total axes by default. Changing the number next to Size will give you more or less axes. Expanding the individual axes will reveal the following:
图 8.4:第一个水平输入轴
Figure 8.4: The first Horizontal input axis
这个词在名称栏中输入的内容将显示在可扩展箭头旁边。在上图,已定义允许水平移动的所有键。
The word entered in the Name slot is what will appear next to the expandable arrow. In the preceding screenshot, all the keys that allow for horizontal movement have been defined.
请注意,键盘的左右箭头以及A和D键默认为水平移动。
Note that the left and right arrows, along with the A and D keys of a keyboard, are defaulted to the Horizontal movement.
列表下方还有第二个水平轴。它配置为与操纵杆或游戏手柄配合使用。
There is also a second Horizontal axis further down the list. It is configured to work with a joystick or a gamepad.
图 8.5:第二个水平输入轴
Figure 8.5: The second Horizontal input axis
就像那里是两个标记为“水平”的轴,它们都可以在代码中使用“水平”标签轻松引用。
As there are two Axes labeled Horizontal, they can both be easily referenced in code with the "Horizontal" label.
笔记
Note
查看列表每个键盘键的关键字以及每个轴输入属性的描述,请访问https://docs.unity3d.com/Manual/class-InputManager.xhtml。
To view a list of the keywords for each keyboard key as well as a description for each of the properties of an axis input, visit https://docs.unity3d.com/Manual/class-InputManager.xhtml.
这样您就可以将所有这些按钮和操纵杆作为一个组引用。这比编写代码让每个单独的键盘键与单独的操纵杆相关联要简单得多。
This will allow you to reference all these buttons and joysticks together as a group. This is much simpler than having to write code that gets each of the individual keyboard keys along the individual joysticks.
您可以通过右键单击并选择“删除数组元素”来删除这 30 个默认轴中的任何一个。
You can delete any of these 30 default axes you want by right-clicking on them and selecting Delete Array Element.
但是,删除它们时要小心。您至少需要一个提交轴和一个取消轴才能使用独立输入管理器(除非您更改独立输入管理器中的提交按钮和取消按钮)。有关更多信息,请参阅本章的独立输入管理器部分。
However, be careful when you delete them. You need at least one Submit axis and one Cancel axis to be able to use the Standalone Input Manager (unless you change the Submit Button and Cancel Button in the Standalone Input Manager). For more information, refer to the Standalone Input Manager section of this chapter.
现在我们已经探索了输入管理器,我们可以查看按钮的各种输入功能以及d键按下。
Now that we have explored the Input Manager, we can review the various input functions for buttons and key presses.
那里通过代码访问按键和按钮按下的方法有很多。具体方法取决于您是否在输入管理器中将按键指定为轴,以及您是否希望按键注册一次或连续注册。我将在本文中讨论其中几种方法,但您可以在https://docs.unity3d.com/ScriptReference/Input.xhtml找到完整的函数列表。
There are quite a few ways to access key and button presses via code. How you do this depends on whether you have the key specified as an axis in the Input Manager and whether you want the key to register once or continuously. I’ll discuss a few in this text, but you can find a full list of the functions at https://docs.unity3d.com/ScriptReference/Input.xhtml.
在本章前面我们回顾的第 8 章示例场景中,一个名为KeyPresses.cs的脚本附加到主摄像头。如果您想尝试一下,KeyPresses.cs脚本包含本节中演示的所有代码按下第键。
A script named KeyPresses.cs is attached to the Main Camera in the Chapter8 example scene we were reviewing earlier in this chapter. The KeyPresses.cs script contains all the code demonstrated in this section if you’d like to play around with key presses.
如果你在输入管理器中有一个按钮定义为轴,你可以使用GetButton()、GetButtonDown()和GetButtonUp()来确定按钮何时被按下。
If you have a button defined as an axis in the Input Manager, you can use GetButton(), GetButtonDown(), and GetButtonUp() to determine when a button has been pressed.
按住按钮时,GetButton()返回true;在按钮最初被按下的帧上,GetButtonDown()仅返回true一次;在按钮被释放的帧上,GetButtonUp()仅返回true一次。
GetButton() returns true while the button is being held, GetButtonDown() returns true only once, on the frame that the button is initially pressed, and GetButtonUp() returns true only once, on the frame that the button is released.
在每个函数中,将输入管理器中的轴名称放在引号内的括号内。通常,这些函数应在脚本的Update()函数中调用,以便可以随时触发它们。
Within each of the functions, you place the axis name from the Input Manager within the parentheses within quotes. Generally, these functions should be called within the Update() function of a script so that they can be triggered at any time.
因此,例如,如果您想检查是否按下了Enter键,由于它被分配给了Submit轴的Positive Button,您可以编写以下代码来在按下Enter键时触发:
So, for example, if you wanted to check whether the Enter key is being pressed, since it is assigned to a Positive Button for the Submit axis, you can write the following code to trigger when the Enter key is pressed down:
无效更新(){
如果(Input.GetButtonDown(“提交”)){
Debug.Log("您按下了提交键/按钮!");
}
}void Update () {
if (Input.GetButtonDown("Submit")){
Debug.Log("You pressed a submit key/button!");
}
} 记住这不会仅通过Enter键触发,因为提交轴有几个键分配给Positive Button和Alt Positive Button。
Keep in mind that this will not just trigger with the Enter key, as the Submit axis has a few keys assigned to the Positive Button and Alt Positive Button.
笔记
Note
需要注意的是,如果你玩第 8 章场景,并想观看这些按钮和按键触发控制台日志消息,你必须首先在游戏视图中单击,以便输入重新游戏中的大师。
It’s important to note that if you play the Chapter8 scene and want to watch these button and key presses fire the console log messages, you must first click within the Game View so that the inputs will register in the game.
如果你寻找一个可以连续触发的功能如果在发射之间没有任何停顿,则需要使用GetAxis()而不是GetButton()。GetButton ()适用于您想要按住但希望在事件发射之间稍作停顿的按钮(想象一下按住射击按钮,枪会在发射子弹之间停顿)。由于这种连续的帧速率独立执行,GetAxis()更适合涉及移动的事件。
If you’re looking for a function that will trigger continuously without any breaks between firing, you want to use GetAxis() rather than GetButton(). GetButton() is good for buttons you want to hold down but want a slight pause between events firing (think of holding down a fire button, and the gun shoots bullets with breaks in between them). GetAxis() works better for events involving movement because of this continuous frame-rate independent execution.
GetAxis() 的工作方式略有不同,因为它返回的是浮点值,而不是布尔值,例如GetButton()。它也最适合在Update()函数中使用。因此,例如,您可以按如下方式检查水平移动是否正在发生:
GetAxis() works a bit differently, as it returns a float value rather than a bool, such as GetButton(). It is also best suited within an Update() function. So, for example, you can check whether the horizontal movement is occurring as follows:
无效更新(){
float HorizontalValue = Input.GetAxis("水平");
如果 (horizontalValue != 0){
Debug.Log("您正在按住水平按钮!");
}
}void Update () {
float horizontalValue = Input.GetAxis("Horizontal");
if (horizontalValue != 0){
Debug.Log("You're holding down a horizontal button!");
}
} 如果你想获取未分配的键盘按键对于轴,您可以使用GetKey()、GetKeyDown()或GetKeyUp()通过 KeyCode引用键盘键。
If you want to get a keyboard key press that is not assigned to an axis, you can use GetKey(), GetKeyDown(), or GetKeyUp() to reference keyboard keys via their KeyCode.
GetKey ()函数的工作原理与GetButton()函数非常相似。当按键被按下时,GetKey()返回true ; GetKeyDown()仅在按键最初被按下的帧上返回true一次;而GetKeyUp()仅在按键被释放的帧上返回true一次。
The GetKey() functions work pretty similar to the GetButton() functions. GetKey() returns true while the key is being held down; GetKeyDown() returns true only once, on the frame that the key is initially pressed; and GetKeyUp() returns true only once, on the frame that the key is released.
每个键都有自己的KeyCode ,需要在GetKey()函数的括号中引用。您可以在https://docs.unity3d.com/ScriptReference/KeyCode.xhtml找到所有键盘KeyCode值的列表。
Each key has its own KeyCode that needs to be referenced in the parentheses of the GetKey() functions. You can find a list of all the keyboard KeyCode values at https://docs.unity3d.com/ScriptReference/KeyCode.xhtml.
因此,例如,如果您想检查是否按下了字母数字键盘上的8键,您可以编写以下代码来在按下8键时触发:
So, for example, if you wanted to check whether the 8 key from the alphanumeric keyboard is being pressed, you could write the following code to trigger when the 8 key is pressed down:
无效更新(){
如果(输入.GetKeyDown(KeyCode.Alpha8)){
Debug.Log("您由于某种原因按下了8键!");
}
}void Update () {
if (Input.GetKeyDown(KeyCode.Alpha8)){
Debug.Log("You pressed the 8 key for some reason!");
}
} 正如使用GetButton()和GetKey(),有三个函数用于检查鼠标按钮是否被按下:GetMouseButton()、GetMouseButtonDown()和GetMouseButtonUp()。它们返回true 的方式与GetButton()和GetKey() 函数相同。
Just as with GetButton() and GetKey(), there are three functions for checking when a mouse button has been pressed: GetMouseButton(), GetMouseButtonDown(), and GetMouseButtonUp(). They return true in the same way that the GetButton() and GetKey() functions do.
您也可以将这些函数放在Update()函数中。在括号内,您可以检查按下的是哪个按钮;0表示左键单击,1表示右键单击,2表示中键单击。
You’d place these functions within the Update() function as well. Within the parentheses, you check to see which button is being pressed; 0 represents a left-click, 1 represents a right-click, and 2 represents a middle-click.
例如,如果您想要检查鼠标中键是否被单击,您可以编写以下代码来在鼠标中键被按下时触发:
So, for example, if you wanted to check that the middle mouse button was clicked, you could write the following code to trigger when the middle mouse button is pressed down:
无效更新(){
如果(输入.GetMouseButtonDown(2)){
Debug.Log("您按下了鼠标中键!");
}
}void Update () {
if (Input.GetMouseButtonDown(2)){
Debug.Log("You pressed the middle mouse button!");
}
} 现在我们已经回顾了按钮和按键的输入功能,让我们回顾一下输入模块。
Now that we’ve reviewed the input function for buttons and key presses, let’s review the Input Modules.
输入模块描述事件系统如何通过鼠标、键盘、触摸屏、游戏手柄等处理游戏输入。你可以将它们视为硬件和事件之间的桥梁。
Input Modules describe how the Event System will handle the inputs to the game via the mouse, keyboard, touchscreen, gamepad, and so on. You can think of them as the bridge between the hardware and events.
Unity提供了三种输入模块:
There are three input modules provided by Unity:
要使用这些输入模块,您需要将它们作为组件附加到EventSystem GameObject。
To utilize these input modules, you attach them as components to your EventSystem GameObject.
您不限于使用这三个输入模块,也可以创建自己的输入模块,因此如果您有一个未被这三个模块之一覆盖的输入设备,您可以创建自己的输入模块脚本,然后将其附加到事件系统。
You are not restricted to using these three input modules and can create your own, so if you have an input device that is not covered by one of those three, you’d create your own input module script and then attach it to the Event System.
还有另一个输入模块,称为触摸输入模块,它曾经是触摸屏输入所必需的。然而,这个模块已被弃用,其功能现在被集中起来进入独立输入模块。由于此输入模块已被弃用,因此本文不再讨论。
There is another input module called Touch Input Module, which used to be necessary for touchscreen inputs. However, this module has been deprecated and its functionality is now lumped into the Standalone Input Module. Since this input module has been deprecated, it will not be discussed in this text.
Let’s look at the three input modules provided by Unity in depth.
独立输入模块是一个非常强大的输入模块,可以与大多数你的输入设备。它可与鼠标、键盘、触摸屏和游戏手柄配合使用。
The Standalone Input Module is a pretty robust input module that will work with most of your input devices. It works with a mouse, keyboard, touchscreen, and gamepad.
创建事件系统游戏对象时,独立输入模块会自动添加到其中。但是,您可以使用对象检查器上的添加组件|事件|独立输入模块将独立输入模块作为组件附加。如果您想添加第二个模块、之前删除了它并想重新附加它,或者想将独立输入模块添加到另一个游戏对象,您可以执行此操作。
The Standalone Input Module is automatically added to your EventSystem GameObject when it is created. However, you can attach the Standalone Input Module as a component using Add Component | Event | Standalone Input Module on the object’s Inspector. You could do this if you wanted to add a second one, previously deleted it, and want to re-attach it, or want to add the Standalone Input Module to another GameObject.
图 8.6:独立输入模块组件
Figure 8.6: The Standalone Input Module component
您将看到独立输入模块的前四个属性是水平轴、垂直轴、提交按钮和取消按钮。这些属性是我在讨论输入模块之前想讨论输入管理器的原因。分配给这些插槽的默认属性是水平、垂直、提交和取消。这些分配引用了输入管理器的轴分配。
You’ll see that the first four properties of the Standalone Input Module are Horizontal Axis, Vertical Axis, Submit Button, and Cancel Button. These properties are the reason I wanted to discuss the Input Manager before discussing the Input Modules. The default properties assigned to these slots are Horizontal, Vertical, Submit, and Cancel. These assignments are referencing the axes assignments from the Input Manager.
每秒输入操作数属性定义每秒允许的输入次数。这与键盘和游戏手柄输入有关。默认值为10。这方法在输入操作之后,下一个输入操作被注册之前会有十分之一秒的延迟。重复延迟属性是每秒输入操作发生之前的时间量(以秒为单位)。
The Input Actions Per Second property defines how many inputs are allowed per second. This is in relation to the keyboard and the gamepad inputs. The default value is 10. This means that there will be a tenth of a second delay after an input action before the next input action is registered. The Repeat Delay property is the amount of time, in seconds, before Input Actions Per Second occurs.
将强制模块活动属性设置为 true 将使该独立输入模块处于活动状态。
Setting the Force Module Active property to true will make this Standalone Input Module active.
笔记
Note
您可以在以下位置了解有关独立输入模块的更多信息:
You can learn more about the Standalone Input Module at the following locations:
https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/script-StandaloneInputModule.xhtml
https://docs.unity3d.com/Packages/com.unity.ugui@1.0/manual/script-StandaloneInputModule.xhtml
https://docs.unity3d.com/2019.1/Documentation/ScriptReference/EventSy词干.StandaloneInputModule.xhtml
BaseInputModule和PointerInputModule是只能通过代码访问的模块。
The BaseInputModule and PointerInputModule are modules that are only accessible via code.
如果你需要创建您的自己的输入模块,您将通过以下方式创建它从BaseInputModule扩展。您可以在https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.BaseInputModule.xhtml查看通过扩展BaseInputModule 可以利用的变量、函数和消息的完整列表。
If you need to create your own Input Module, you will create it by extending from the BaseInputModule. You can view a full list of the variables, functions, and messages that can be utilized by extending the BaseInputModule at https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.BaseInputModule.xhtml.
PointerInputModule是前面描述的独立输入模块使用的BaseInputModule。它还可用于编写自定义输入模块。您可以在https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.PointerInputModule.xhtml上查看可通过扩展PointerInputModule使用的变量、函数和消息的完整列表。
The PointerInputModule is a BaseInputModule that is used by the Standalone Input Module described earlier. It can also be used to write custom Input Modules. You can view a full list of the variables, functions, and messages that can be utilized by extending the PointerInputModule at https://docs.unity3d.com/Packages/com.unity.ugui@1.0/api/UnityEngine.EventSystems.PointerInputModule.xhtml.
现在,让我们看看如何访问多点触控输入适用于移动设备和触摸屏设备。
Now, let’s look at how we can access multi-touch input on mobile and touchscreen devices.
访问多点触摸非常简单。您可以使用Input.GetTouch(index)访问触摸,其中索引表示触摸的索引,第一个触摸发生在索引0处。从那里,您可以以几乎相同的方式访问信息就像访问鼠标信息一样。您还可以使用Input.touchCount了解总共发生了多少次触摸。有关如何访问多点触摸输入的示例,请参阅本章的示例部分。
Accessing multi-touch is pretty easy. You access touches with Input.GetTouch(index), where the index represents the index of the touch, with the first touch occurring at index 0. From there, you can access information pretty much in the same way as accessing information about a mouse. You can also find out how many total touches are occurring with Input.touchCount. See the Examples section of this chapter for an example of how to access multi-touch input.
移动设备还配有加速度计和陀螺仪,为设备提供输入。让我们看看如何访问这些输入。
Mobile devices also have accelerometers and gyroscopes providing input to the device. Let’s look at how you can access those inputs.
您可以访问使用Vector3 Input.acceleration属性从设备的加速度计获取数据。Input.acceleration的坐标根据设备的旋转与场景对齐,如下所示:
You can access data from the device’s accelerometer using the Vector3 Input.acceleration property. The coordinates of Input.acceleration line up with the scene based on the rotation of the device, as shown:
图 8.7:基于屏幕旋转的世界轴
Figure 8.7: The world axes based on screen rotation
简单示例其中涉及在移动设备时在场景中移动物体,在对象的Update()函数中使用类似下面的内容:
Simple examples of this involve moving an object around a scene when the device is moved, using something like the following within an Update() function on the object:
变换.平移(输入.加速度.x, 0,-输入.加速度.y);
transform.Translate(Input.acceleration.x, 0, -Input.acceleration.y);
陀螺仪使用更复杂的数学方法,通过Gyroscope类获得更精确的屏幕运动。请记住,许多设备都不支持陀螺仪,因此最好尽可能使用加速度计。
The gyroscope uses more complicated mathematics to get a more precise movement of the screen using the Gyroscope class. Remember, the gyroscope is not supported on many devices, so it’s best to use the accelerometer when possible.
笔记
Note
有关如何在 iOS 设备上使用陀螺仪的示例,请参见此处:https://docs.unity3d.com/ScriptReference/Gyroscope.xhtml。
An example of how to use the gyroscope on an iOS device can be found here: https://docs.unity3d.com/ScriptReference/Gyroscope.xhtml.
现在我们已经回顾了各种输入模块,让我们回顾一下事件触发器。
Now that we’ve reviewed the various input modules, let’s review Event Triggers.
事件触发器组件可以附加到任何 UI(或非 UI)元素,以允许对象接收事件。一些 UI 元素已预先配置为拦截特定事件。例如,按钮具有onClick事件。但是,如果您想向尚未设置为接收事件的对象添加事件,或者您希望它接收不同的事件,您可以将事件触发器组件附加到GameObject。
The Event Trigger component can be attached to any UI (or non-UI) element to allow the object to receive events. Some of the UI elements are preconfigured to intercept specific events. For example, buttons have the onClick event. However, if you’d like to add an event to an object that either isn’t already set up to receive events or you want it to receive different events, you can attach an Event Trigger component to the GameObject.
您可以通过选择添加组件|事件|事件触发器来附加事件触发器组件。
You can attach an Event Trigger component by selecting Add Component | Event | Event Trigger.
使用事件触发器组件的一个注意事项是,它所附加的对象会接收所有事件,而不仅仅是您添加的事件。因此,即使您没有告诉对象如何处理指定的事件,它也会接收该事件并确认事件已发生 - 它只是不会做出任何响应。这可能会降低游戏的性能。如果您担心性能,您需要编写自己的脚本,仅将要使用的事件附加到您的组件。下一节“事件输入”将讨论如何实现这一点。
One caveat of using the Event Trigger component is that the object it is attached to receives all the events, not just the ones you added. So, even if you don’t tell the object what to do with the specified event, it will receive that event and acknowledge that the event occurred—it just won’t do anything in response. This can slow the performance of your game. If you are worried about performance, you will want to write your own script that attaches only the events you want to use to your component. The next section, Event Inputs, discusses how to achieve this.
如果在 UI 元素以外的对象上使用事件触发器组件,则该对象还必须具有对撞机组件,并且必须在场景内的相机上包含射线投射器。
If you use an Event Trigger component on an object other than a UI element, the object must also have a collider component, and you must include a raycaster on the camera within the scene.
使用哪种对撞机和射线投射器取决于您是在 2D还是 3D 中工作。
Which collider and raycaster you use depends on whether you are working in 2D or 3D.
如果您在 2D 中工作,则可以使用添加组件|物理 2D向对象添加 2D 碰撞器,然后从对象的检查器中选择适当的 2D 碰撞器。然后,您可以通过在相机的检查器中选择添加组件|事件|物理 2D 射线投射器向相机添加射线投射器。
If you are working in 2D, you can add a 2D collider to the object with Add Component | Physics 2D and then select the appropriate 2D collider from within the object’s Inspector. You can then add a raycaster to the camera by selecting Add Component | Event | Physics 2D Raycaster from within the camera’s Inspector.
如果您在 3D 环境中工作,则可以使用“添加组件” | “物理”将 3D 碰撞器添加到对象,然后从对象的检查器中选择适当的 3D 碰撞器。然后,您可以通过在相机的检查器中选择“添加组件” | “事件” | “物理射线投射器”将射线投射器添加到相机。
If you are working in 3D, you can add a 3D collider to the object with Add Component | Physics and then select the appropriate 3D collider from within the object’s Inspector. You can then add a raycaster to the camera by selecting Add Component | Event | Physics Raycaster from within the camera’s Inspector.
Let’s look at the various event types that the Event Trigger can receive.
你可以告诉通过选择“添加新事件类型”来选择您想要接收哪种类型的输入事件的对象。
You can tell the object which type of input event you want to receive by selecting Add New Event Type.
许多这些事件与对象的边界区域相关。UI 对象的边界区域由 Rect Transform 的区域表示。对于非 UI 对象,边界区域由 Rect Transform 的区域表示。离子由二维或三维对撞机表示。
Many of these events are tied to the bounding region of the object. The bounding region of a UI object is represented by the area of the Rect Transform. For a non-UI object, the bounding region is represented by a 2D or 3D collider.
指针事件可以通过独立输入模块中的指针调用。请记住,指针并非仅仅是鼠标。独立输入模块中的指针可以是鼠标、手指触摸或与游戏手柄移动绑定的十字线。
Pointer events can be called by the pointer in a Standalone Input Module. Remember that a pointer is not exclusively a mouse. The pointer in a Standalone Input Module can be a mouse, finger touch, or a reticle tied to gamepad movement.
其中两种事件类型与指针相对于对象边界框区域的位置有关。当指针进入对象的边界框时,将调用PointerEnter事件;当指针离开边界框区域时,将调用PointerExit事件。
Two of the event types are related to the position of the pointer in relation to the object’s bounding box region. The PointerEnter event is called when the pointer enters the bounding box of the object and PointerExit is called when the pointer exits the bound box area.
有三个事件与单击对象有关。当在对象的边界区域内按下指针时,将调用PointerDown事件;当在对象的边界区域内释放指针时,将调用PointerUp 事件。需要注意的是,使用PointerUp时,可以在对象外部按下指针,按住,然后在对象内部释放,以触发PointerUp事件。当按下指针然后释放时,将调用PointerClick事件d 在对象的边界区域内。
There are three events related to clicking on the object. The PointerDown event is called when the pointer is pressed down within the bounding region of the object, and PointerUp is called when the pointer is released within the bounding region of the object. It’s important to note that with PointerUp, the pointer can be pressed outside of the object, held down, and then released inside the object for the PointerUp event to trigger. The PointerClick event is called when the pointer is pressed and then released within the bounding region of the object.
工作时对于各种拖放事件,区分被拖动的对象和拖动对象所放置的对象非常重要。
When working with the various drag and drop events, it’s important to differentiate between the object being dragged and the object on which the dragged object is dropped.
无论何时发现拖动对象,但在实际拖动对象之前,都会调用InitializePotentialDrag事件。
The InitializePotentialDrag event is called whenever a drag object is found, but before an object is actually being dragged.
当拖动对象时,会在该对象上调用Drag事件。当在对象的边界框内按下指针,然后移动而不释放时,会发生Drag事件。释放指针即可结束该事件。当拖动对象开始拖动时,会从该对象调用BeginDrag事件;当拖动结束时,会调用EndDrag事件。
The Drag event is called on the object being dragged when it is being dragged. A Drag event occurs when a pointer is pressed within the bounding box of an object and then moved without releasing. It’s ended by releasing the pointer. The BeginDrag event is called from the object being dragged when its drag begins, and the EndDrag event is called when its drag ends.
Drop事件与EndDrag事件不同。EndDrag事件在刚刚被拖动的对象上调用。Drop事件由拖动对象被放置到的对象调用。因此,当拖动对象停止拖动时, Drop事件由触摸拖动对象的对象调用。因此,如果您正在制作拖放菜单,则需要将Drag事件添加到要拖动的对象,并将Drop 事件将被放入他们将被放入的插槽中。
The Drop event is different from the EndDrag event. The EndDrag event is called on the object that was just being dragged. The Drop event is called by the object on which the dragged object was dropped. Therefore, the Drop event is called by the object touching the dragged object when the dragged object stops dragging. So, if you were making a drag and drop menu, you’d add the Drag event to the objects you want to drag and the Drop event to the slots they will be dropped into.
选择事件是当对象被视为选定对象时调用,当对象不再被视为选定时调用Deselect 。每个事件都只触发一次 - 当对象被视为选定或取消选定时。如果您想要一个在对象被选定时持续触发的事件,您可以使用UpdateSelected事件。每帧都会调用UpdateSelected事件。
The Select event is called when the object is considered the selected object and Deselect is called when the object is no longer considered selected. Each of these events only fires once—the moment the object is considered selected or deselected. If you want an event that will trigger continuously while the object is selected, you can use the UpdateSelected event. The UpdateSelected event is called every frame.
其他活动根据输入管理器中的分配进行调用。请记住,您可以将按钮、键等分配给定义移动、提交和取消的轴。让我们讨论一下其中的一些事件。
Other events are called based on assignments in the Input Manager. Remember that you can assign buttons, keys, and such to axes that define movement, submit, and cancel. Let’s talk about a few of these events.
当鼠标滚轮滚动时,将调用 Scroll 事件;当发生移动时,将调用Move事件。当按下分配给Submit轴的按钮时,将调用Submit事件;当按下分配给Cancel轴被按下时,取消事件被调用。
The Scroll event is called when the mouse wheel scrolls and the Move event is called when a movement happens. When the button assigned to the Submit axis is pressed, the Submit event is called and when the button assigned to the Cancel axis is pressed, the Cancel event is called.
一旦你真正选择事件类型后,您必须指定触发该事件类型时将发生什么。以下屏幕截图显示了选择Pointer Enter作为事件类型的结果:
Once you have actually selected an event type, you must specify what will happen when that event type triggers. The following screenshot shows the results of selecting Pointer Enter as an event type:
图 8.8:事件触发器组件
Figure 8.8: The Event Trigger component
这上图显示已选择Pointer Enter事件类型,但尚未定义当指针进入对象边界区域时会发生什么。要定义触发事件时会发生什么,您必须选择事件框右下角的+号。您可以通过多次选择+号来添加触发事件时的多个操作。
The preceding screenshot shows that an event type of Pointer Enter has been selected, but what happens when the pointer enters the object’s bounding area is yet to be defined. To define what happens when the event triggers, you must select the + sign at the bottom-right corner of the event’s box. You can add multiple actions when the event triggers by selecting the + sign multiple times.
一旦事件类型被添加到事件触发器组件,它就不能再被添加,并且会在添加新事件类型列表中显示为灰色。
Once an event type has been added to the Event Trigger component, it cannot be added a second time and will be grayed out in the Add New Event Type list.
要从事件触发器组件中删除事件类型,请选择事件类型框右上角的-符号。
To remove an event type from the Event Trigger component, select the – sign at the top-right corner of the event type’s box.
一旦选择加号,事件类型应该如下所示:
Once the plus sign is selected, the event type should look as follows:
图 8.9:带有指针进入事件的事件触发器组件
Figure 8.9: The Event Trigger component with a Pointer Enter event
此事件的第一个设置是一个下拉菜单,其中包含“仅运行时”(默认情况下)、“编辑器和运行时”和“关闭”选项。在这里我们指定何时可以触发事件。将其设置为“关闭”将使事件永远不会触发。将其设置为“仅运行时”将在玩游戏时触发事件。将其设置为“编辑器和运行时”将在玩游戏时触发事件,但它也会在游戏未处于播放模式时接受编辑器中的触发器。大多数情况下,“仅运行时”足以满足您的要求,因此它是默认设置。
The first setting on this event is a dropdown menu with the Runtime Only (by default), Editor and Runtime, and Off options. This is where we specify when the event can be triggered. Setting this to Off will make the event never trigger. Setting this to Runtime Only will have the event trigger when the game is being played. Setting this to Editor and Runtime will make events trigger when the game is being played, but it also accepts the triggers in the Editor when the game is not in play mode. Most of the time, Runtime Only is sufficient for what you will be doing and hence it is the default.
低于此下拉菜单是一个无(对象)的插槽。您需要从层次结构中将要运行的函数所附加的任何项目拖放到此插槽中。分配完成后,附加到该对象的所有可用组件和脚本的列表将显示在第二个下拉菜单中。您可以将事件触发器所附加的对象拖放到此插槽中,而不限于仅使用其他对象。
Below that dropdown menu is a slot with None (Object) in it. You are to drag from the Hierarchy whichever item the function you want to run is attached to into this slot. Once that is assigned, a list of all the available components and scripts attached to that object will display in the second dropdown menu. You can drag and drop the object the Event Trigger is attached to in this slot and are not restricted to only using other objects.
以下屏幕截图显示了一个添加了事件触发器和Pointer Enter事件类型的Image GameObject 。相同的图像被添加到插槽中,表示查看其自身的组件。当指针进入其Rect Transform时,图像组件的 sprite 属性将更改为foodSpriteSheet_1精灵。
The following screenshot shows an Image GameObject with an Event Trigger added to it and the Pointer Enter event type. The same image is added to the slot, signifying to look at the components on itself. The image component’s sprite property is to change to the foodSpriteSheet_1 sprite when the pointer enters its Rect Transform.
图 8.10:带有用于交换精灵的“指针进入”事件的事件触发器组件
Figure 8.10: The Event Trigger component with a Pointer Enter event that swaps a sprite
要查看此事件触发器的运行情况,请播放第 8 章场景。将鼠标悬停在图像上。它最初看起来像一个药水瓶,但当您的鼠标悬停在它上面时,它会变成三角形。
To see this Event Trigger in action, play the Chapter8 scene. Hover your mouse over the image. It will initially look like a potion bottle but will change to a triangle when your mouse hovers over it.
您还可以在附加到对象的脚本中运行函数。例如,下一个屏幕截图显示了相同的图像,但现在也带有指针点击事件。主摄像头附加了一个名为HelloWorld.cs的脚本,其中有一个名为HeyThere() 的函数。
You can also run functions within scripts attached to objects. For example, the next screenshot shows the same image but now with a Pointer Click event as well. Main Camera has a script attached to it called HelloWorld.cs with a function called HeyThere().
图 8.11:事件触发器组件,带有触发方法的指针单击事件
Figure 8.11: The Event Trigger component with Pointer Click event that triggers a method
HeyThere ()函数每当单击右侧的图像时,都会在控制台中打印“Hello world!这是主摄像头在讲话!” 。
The HeyThere() function simply prints Hello world! This is main camera speaking! in the Console whenever the image to the right is clicked.
要从事件触发器组件运行函数,它必须是公共的、返回类型为 void,并且参数不超过一个。
To run a function from the Event Trigger component, it must be public, have a return type of void, and have no more than one parameter.
现在,让我们回顾一下如何编写与 Event Tri 类似的代码gger 组件,通过使用事件输入。
Now, let’s review how we can write code that performs similarly to the Event Trigger component through the use of event inputs.
正如事件触发器部分所述,您可能不想使用事件触发器组件,因为事件触发器组件会导致其所附加的对象接收事件触发器部分列出的所有事件。因此,如果您担心性能问题,您将需要另一种方法来接收对象上的事件。
As stated in the Event Trigger section, you may not want to use an Event Trigger component because the Event Trigger component causes the object on which it is attached to receive all the events listed in the Event Trigger section. So, if you are worried about performance issues, you will want an alternate way to receive events on an object.
事件触发器部分中可添加的所有事件类型也可以通过代码添加到对象中,而无需使用事件触发器组件。要使用没有事件触发器组件的事件,您必须从适当的接口派生脚本并了解事件使用的事件数据类的类型。
All event types that were available to add in the Event Trigger section can also be added to an object via code without using the Event Trigger component. To use an event without the Event Trigger component, you must derive your script from the appropriate interface and know the type of event data class that the event uses.
接口模板定义了类可以实现的所有必需功能。因此,通过使用接口,您可以使用该接口中定义的任何方法或函数。我将向您展示一些如何执行此操作的示例,但首先,让我们看看可用的事件及其所需的接口。
An interface is a template that defines all the required functionality that a class can implement. So, by using an interface, you can then use any of the methods or functions that have been defined within that interface. I’ll show you some examples of how to do this, but first, let’s look at the available events and their required interfaces.
有三个类可以派生事件数据,分别是PointerEventData,AxisEventData和BaseEventData:
There are three classes that the event data can be derived from, which are PointerEventData, AxisEventData, and BaseEventData:
有一个第四个事件数据类,AbstractEventData。其他三个类都继承自该类。
There is a fourth event data class, AbstractEventData. It is the class from which the other three inherit.
下表列出了StandaloneInputModule可用的事件列表及其所需的接口和事件数据类。为了保持连续性,事件的列出顺序与事件触发器组件中的列出顺序相同:
The list of events available for a StandaloneInputModule along with their required interfaces and event data class are provided in the following chart. The events are listed in the same order they are listed within the Event Trigger component for continuity purposes:
|
事件 Event |
界面 Interface |
事件数据类型 Event Data Type |
|
指针进入时 OnPointerEnter |
指针输入处理程序 IPointerEnterHandler |
指针事件数据 PointerEventData |
|
退出指针 OnPointerExit |
退出处理程序 IPointerExitHandler |
指针事件数据 PointerEventData |
|
指针向下时 OnPointerDown |
指针向下处理程序 IPointerDownHandler |
指针事件数据 PointerEventData |
|
指针向上移动 OnPointerUp |
指针向上处理程序 IPointerUpHandler |
指针事件数据 PointerEventData |
|
指针点击事件 OnPointerClick |
指针点击处理程序 IPointerClickHandler |
指针事件数据 PointerEventData |
|
拖拽时 OnDrag |
拖拽处理器 IdragHandler |
指针事件数据 PointerEventData |
|
滴滴 OnDrop |
删除处理程序 IdropHandler |
指针事件数据 PointerEventData |
|
滚动时 OnScroll |
滚动处理程序 IscrollHandler |
指针事件数据 PointerEventData |
|
更新选定时 OnUpdateSelected |
更新选定处理程序 IUpdateSelectedHandler |
基本事件数据 BaseEventData |
|
选定时 OnSelect |
选取处理程序 IselectHandler |
基本事件数据 BaseEventData |
|
取消选择时 OnDeselect |
思想选择处理程序 IdeselectHandler |
基本事件数据 BaseEventData |
|
移动 OnMove |
移动处理程序 IMoveHandler |
轴事件数据 AxisEventData |
|
初始化潜在阻力 OnInitializePotentialDrag |
IInitializePotentialDragHandler IInitializePotentialDragHandler |
指针事件数据 PointerEventData |
|
开始拖动时 OnBeginDrag |
开始拖动处理程序 IbeginDragHandler |
指针事件数据 PointerEventData |
|
拖拽结束 OnEndDrag |
结束拖拽处理程序 IEndDragHandler |
指针事件数据 PointerEventData |
|
提交时 OnSubmit |
提交处理程序 ISubmitHandler |
基本事件数据 BaseEventData |
|
取消时 OnCancel |
取消处理程序 ICancelHandler |
基本事件数据 BaseEventData |
表 8.1:各种事件的接口和事件数据类型
Table 8.1: Interfaces and event data types for the various events
To write a class with one of these events, you will use the following template:
使用 UnityEngine;
使用 UnityEngine.UI;
使用 UnityEngine.EventSystems;
公共类类名:MonoBehaviour,接口名称{
公共无效事件名称(事件数据类型名称事件数据){
//事件触发后会发生什么
}
}using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class ClassName : MonoBehaviour, InterfaceName{
public void EventName(EventDataTypeName eventData){
//what happens after event triggers
}
} 上述代码中突出显示的项目将被上表中的项目替换。
The items highlighted in the preceding code will be replaced by the items within the preceding table.
例如,如果您想要实现OnPointerEnter事件,则在将突出显示的代码替换为适当的事件、接口和事件数据类型后,代码将如下所示:
For example, if you wanted to implement an OnPointerEnter event, the code would look as follows after the highlighted code has been replaced with an appropriate event, interface, and event data type:
使用 UnityEngine;
使用 UnityEngine.UI;
使用 UnityEngine.EventSystems;
公共类 ClassName : MonoBehaviour,IPointerEnterHandler
公共 void OnePointerEnter ( PointerEventData eventData){
//事件触发后会发生什么
}
}using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
public class ClassName : MonoBehaviour, IPointerEnterHandler
public void OnePointerEnter(PointerEventData eventData){
//what happens after the event triggers
}
} 你必须包括UnityEngine.EventSystems命名空间,以便使用事件数据编写代码。UnityEngine.UI命名空间是可选的,只有当您还要为UI 对象编写事件时才是必需的。
You must include the UnityEngine.EventSystems namespace to write code with event data. The UnityEngine.UI namespace is optional and is only required if you will also be writing your events for UI objects.
现在我们已经了解了发送和接收事件的各种方式,让我们来看看射线投射器。
Now that we’ve reviewed various ways to send and receive events, let’s look at raycasters.
记住事件系统跟踪光线投射以及所有其他我们已经讨论过的事情。光线投射用于通过将光线从用户指针投射到场景中来确定与哪些 UI 元素进行交互。该射线被认为起源于相机的平面,然后向前穿过场景。这条射线击中的任何东西都会得到交互。您可以让射线继续穿过它击中的第一个 UI 元素,也可以停在它击中的第一个 UI 元素处。要让射线停在它击中的第一个 UI 元素处,对象必须阻止光线投射。这将阻止它后面的项目进行交互。接下来,我们将讨论射线投射器的类型秒。
Remember that the Event System keeps track of raycasting along with all the other things we have discussed. Raycasting is used to determine which UI elements are being interacted with by projecting a ray from the user’s pointer into the scene. This ray is considered to originate at the camera’s plane and then proceed forward through the scene. Whatever this ray hits receives an interaction. You can have the ray continue through the first UI element it hits or stop at the first UI element it hits. To get a ray to stop at the first UI element it hits, the object must block raycasting. This will stop items behind it from being interacted with. Next, we’ll discuss the types of raycasters.
当画布添加到场景后,它会自动给定一个Graphic Raycaster组件。
When a Canvas is added to the scene, it is automatically given a Graphic Raycaster component.
这是射线投射系统,可让您与该 Canvas 的所有子 UI 对象进行交互。它有三个属性:忽略反转图形、阻塞对象和阻塞蒙版。
This is the raycasting system that will allow you to interact with all UI objects that are children of that Canvas. It has three properties: Ignore Reversed Graphics, Blocking Objects, and Blocking Mask.
“忽略反向图形”切换按钮决定了当 Canvas 中的图形对象面向后方(相对于光线投射器)时,是否可以与其交互。“阻挡对象”和“阻挡蒙版”属性允许您分配类型的位于画布前方(相机和画布之间)的物体可能会阻挡光线投射到帆布。
The Ignore Reversed Graphics toggle determines whether or not graphical objects within the Canvas can be interacted with if they are facing backward (in relation to the raycaster). The Blocking Objects and Blocking Mask properties allow you to assign the types of objects that are in front of the Canvas (between the camera and the Canvas) that can block raycasting to the Canvas.
如前所述,如果你想使用事件系统非 UI 对象,您必须将 Raycaster 组件附加到场景中的相机。您可以根据使用的是 2D还是 3D,将Physics 2D Raycaster或Physics Raycaster (或两者)添加到相机。
As stated earlier, if you want to use the Event System with a non-UI object, you must attach a Raycaster component to a camera within the scene. You can add either a Physics 2D Raycaster or a Physics Raycaster (or both) to your camera based on whether you are using 2D or 3D.
在相机的检查器中,您可以通过选择添加组件|事件|物理 2D 射线投射器来添加物理 2D 射线投射器,也可以通过选择添加组件|事件|物理射线投射器来添加物理射线投射器。
From within the camera’s inspector, you can add the Physics 2D Raycaster by selecting Add Component | Event | Physics 2D Raycaster and the Physics Raycaster by selecting Add Component | Event | Physics Raycaster.
两个组件显示如下:
The two components appear as follows:
图 8.12:两种类型的物理射线投射器
Figure 8.12: The two types of Physics Raycasters
事件掩码属性决定哪些类型的对象可以接收光线投射。
The Event Mask property determines which types of objects can receive raycasting.
如果您尝试将其中一个组件添加到非相机游戏对象 (GameObject),相机组件也将自动附加到该游戏对象 (GameObject) 。
If you attempt to add either of these components to a non-camera GameObject, a Camera component will automatically be attached to the GameObject as well.
现在我们已经回顾了可用于为 UI 编程交互的各种系统,让我们看一些例子。
Now that we’ve reviewed the various systems that we can use to program interactions for our UI, let’s look at some examples.
我们将继续开发前两章中构建的 UI。为了帮助组织项目,请复制您在上一章中创建的Chapter7场景;它将自动命名为Chapter8。
We will continue to work on the UI we have been building for the last two chapters. To help organize the project, duplicate the Chapter7 scene that you created in the last chapter; it will automatically be named Chapter8.
笔记
Note
如果您没有完成第 6 章和第7 章的示例,但想完成本章中的示例,则可以导入标有第 08 章- 示例 1 - 开始的包。您还可以在第 08 章- 示例 1 -结束中查看已完成的示例 包裹。
If you did not work through the examples for Chapter 6, and Chapter 7, but would like to work through the examples in this chapter, you can import the package labeled Chapter 08 – Examples 1 - Start. You can also view the completed examples in the Chapter 08 – Examples 1 – End package.
到目前为止,我们已经我们计划将两个面板变成弹出窗口:第 6 章中的暂停面板和第 7 章中的库存面板。目前,它们都在场景中可见(即使暂停面板隐藏在库存面板后面)。我们希望当我们按下P和I时它们会弹出键盘。为了演示目的,我们将以不同的方式访问每个面板的键盘键。
So far, we have made two Panels that we plan on turning into popups: the Pause Panel from Chapter 6, and the Inventory Panel from Chapter 7. Right now, they are both visible in the scene (even though Pause Panel is hidden behind the Inventory Panel). We want them to pop up when we press P and I on the keyboard. For demonstration purposes, we’ll access the keyboard keys differently for each Panel.
请记住,这两个面板上都有 Canvas Group 组件。这些组件将使我们能够轻松访问面板的 alpha、intractable 和块raycasts p绳索。
Remember that both of these Panels have Canvas Group components on them. These components will allow us to easily access the Panels’ alpha, intractable, and blocks raycasts properties.
让我们从库存面板开始。我们希望当按下键盘上的I键时,面板会弹出和关闭。要使用I键显示和隐藏库存面板,请完成以下步骤:
Let’s begin with the Inventory Panel. We want the Panel to pop up and close when the I key is pressed on the keyboard. To make the Inventory Panel appear and disappear with the I key, complete the following steps:
使用System.Collections;
使用 System.Collections.Generic;
使用 UnityEngine;
公共类 ShowHidePanels : MonoBehaviour {
公共 CanvasGroup 库存面板;
}CanvasGroup变量类型虽然与 UI 元素一起使用,但不在UnityEngine.UI命名空间中,而是在UnityEngine命名空间中,因此我们目前不需要在脚本中包含UnityEngine.UI命名空间。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ShowHidePanels : MonoBehaviour {
public CanvasGroup inventoryPanel;
}The CanvasGroup variable type, even though it is used with UI elements, is not in the UnityEngine.UI namespace, but the UnityEngine namespace, so we do not need to include the UnityEngine.UI namespace in our script at the moment.
bool inventoryUp = false;
bool inventoryUp = false;
使用系统;
将以下方法添加到您的脚本中:
公共无效TogglePanel(CanvasGroup面板,bool显示)
{
Panel.alpha = Convert.ToInt32(显示);
面板.interactable = 显示;
面板.blocksRaycasts = 显示;
}正如您所看到的,方法有两个参数。第一个参数是名为Panel 的CanvasGroup ,第二个参数是名为show 的布尔值。当show为false时,它将alpha属性设置为0,当show为true时,它将 alpha 属性设置为1。当show为false时,它还会将interactable和blocksRaycasts属性设置为false,当show为true时,它将设置为true 。
using System;
Add the following method to your script:
public void TogglePanel(CanvasGroup Panel, bool show)
{
Panel.alpha = Convert.ToInt32(show);
Panel.interactable = show;
Panel.blocksRaycasts = show;
}As you can see, the method has two parameters. The first parameter is a CanvasGroup called Panel and the second parameter is a Boolean called show. It will set the alpha property to 0 when show is false and 1 when show is true. It will also set the interactable and blocksRaycasts properties to false when show is false and true when show is true.
无效开始(){
切换面板(库存面板,inventoryUp);
}void Start () {
TogglePanel(inventoryPanel, inventoryUp);
}无效更新(){
//库存面板
如果(输入.GetKeyDown(KeyCode.I)){
}
}void Update () {
//inventory Panel
if(Input.GetKeyDown(KeyCode.I)){
}
}将以下突出显示的代码添加到Update()函数中:
无效更新()
{
// 库存面板
如果(输入.GetKeyDown(KeyCode.I))
{
库存上升 = !库存上升;
切换面板(库存面板,inventoryUp);
}
}Add the following highlighted code to your Update() function:
void Update()
{
// Inventory Panel
if (Input.GetKeyDown(KeyCode.I))
{
inventoryUp = !inventoryUp;
TogglePanel(inventoryPanel, inventoryUp);
}
}图 8.13:ShowHidePanel.cs 脚本组件
Figure 8.13: The ShowHidePanel.cs script component
图 8.14:添加库存面板 ShowHidePanel.cs 脚本组件
Figure 8.14: Adding the Inventory Panel ShowHidePanel.cs script component
现在我们已经完成了显示和隐藏库存面板所需的工作,我们可以继续暂停面板。
Now that we’ve completed the work needed to show and hide the Inventory Panel, we can move on to the Pause Panel.
现在,让我们开始吧暂停面板也一样。我们的做法与库存面板略有不同。为了确保您知道如何使用输入管理器访问按键,我们将使用输入管理器而不是KeyCode。我们还需要暂停游戏。
Now, let’s do the same thing for the Pause Panel. We’ll do this slightly differently than the Inventory Panel. To make sure that you can see how to access a key with the Input Manager, we’ll use the Input Manager instead of a KeyCode. We also need to actually pause the game.
要使用P键显示暂停面板并暂停游戏,请完成以下步骤:
To display the Pause Panel using the P key and pause the game, complete the following steps:
图 8.15:带有额外轴的输入管理器
Figure 8.15: The Input Manager with an extra axis
图 8.16:添加到输入管理器的暂停轴
Figure 8.16: The Pause axis added to the Input Manager
公共 CanvasGroup pausePanel; bool pauseUp = false;
public CanvasGroup pausePanel;bool pauseUp = false;
切换面板(pausePanel,pauseUp);
TogglePanel(pausePanel, pauseUp);
// 暂停面板
如果(Input.GetButtonDown("暂停")){
暂停 = !暂停;
切换面板(pausePanel,pauseUp);
}// pause Panel
if(Input.GetButtonDown("Pause")){
pauseUp = !pauseUp;
TogglePanel(pausePanel, pauseUp);
}图 8.17:添加了暂停面板的 ShowHidePanel.cs 脚本组件
Figure 8.17: The ShowHidePanel.cs script component with the Pause Panel added
Next, we’ll learn about pausing the game.
这游戏现在实际上并没有暂停。如果我们在场景中运行动画或事件,即使暂停面板打开,它们也会继续运行。暂停游戏的一个非常简单的方法是操纵游戏的时间刻度。如果时间刻度设置为1,时间将照常运行。如果时间刻度设置为0,游戏内的时间将暂停。
The game doesn’t actually pause right now. If we had animations or events running in the scene, they would continue to run even with the Pause Panel up. A really easy way to pause a game is to manipulate the time scale of the game. If the time scale is set to 1, time will run as it normally does. If the time scale is set to 0, the time within the game will pause.
此外,我们当前的设置无法像预期的那样正常工作。库存面板和暂停面板可以同时显示。如果库存面板已打开,则暂停面板将被其覆盖,因为它正在其后面渲染。此外,当游戏暂停时,可以激活库存面板。
Also, our current setup doesn’t quite work as a pause menu would be expected to. Inventory Panel and Pause Panel can be displayed at the same time. If Inventory Panel is up, the Pause Panel is covered up by it since it is rendering behind it. Also, the Inventory Panel can be activated when the game is paused.
我们需要暂停游戏的时间尺度,更改面板渲染的顺序,并在游戏暂停时禁用功能,以使暂停面板正常运行。要创建正常运行的暂停面板,请完成以下步骤:
We’ll need to pause the time scale of our game, change the order that our Panels render, and disable functionality when the game is paused to have a Pause Panel that functions properly. To create a properly functioning Pause Panel, complete the following steps:
// 暂停面板
如果(Input.GetButtonDown("暂停")){
暂停 = !暂停;
切换面板(pausePanel,pauseUp);
时间.timeScale = Convert.ToInt32(pauseUp);
}// pause Panel
if(Input.GetButtonDown("Pause")){
pauseUp = !pauseUp;
TogglePanel(pausePanel, pauseUp);
Time.timeScale = Convert.ToInt32(pauseUp);
}图 8.18:弹出画布的子项
Figure 8.18: The children of the Popup Canvas
如果(Input.GetKeyDown(KeyCode.I)&&!pauseUp){if(Input.GetKeyDown(KeyCode.I) && !pauseUp){重要的是请记住,当您有暂停面板时,需要关闭其他事件。将时间刻度设置为0不会阻止其他事件发生的能力;它实际上只会停止动画和您可能显示的任何使用时间刻度的时钟。因此,我们需要确保我们编程的任何其他事件都已关闭当游戏暂停时。
It’s important to remember that when you have a Pause Panel, other events need to be turned off. Setting the timescale to 0 does not stop the ability for other events to occur; it only really stops animations and any clocks you may have displayed that use the time scale. So, we will need to ensure that any other event we program is turned off when the game is paused.
我们有一个可以显示和隐藏的库存面板和一个HUD库存。我希望能够将对象从较大的库存面板拖到我们在上一章中创建的较小的 HUD 库存(称为右下面板)中。
We have an Inventory Panel that can be displayed and hidden and a HUD inventory. I want to be able to drag objects from my larger Inventory Panel to my smaller HUD inventory called Bottom Right Panel that we created in the previous chapter.
为了让事情变得简单一点,让我们禁用本章前面添加到主摄像头的ShowHidePanels脚本。您可以通过取消选择主摄像头上脚本组件旁边的复选框来执行此操作:
To make things a little easier for ourselves, let’s disable the ShowHidePanels script that we added to the Main Camera earlier in this chapter. You can do this by deselecting the checkbox next to the script’s component on the Main Camera:
图 8.19:禁用 ShowHidePanel.cs 脚本组件
Figure 8.19: Disabling the ShowHidePanel.cs script component
我们还要禁用“暂停面板”,这样它就不会妨碍我们了。方法是取消选中“暂停面板”检查器中其名称旁边的复选框。
Let’s also disable Pause Panel so that it will not be in our way. Do this by deselecting the checkbox next to the name of the Pause Panel in its Inspector.
现在,我们的面板将保持可见,使我们更容易调试我们即将编写的代码。
Now, our Panel will stay visible, making it easier for us to debug the code we’re about to write.
有制作拖放机制的方法有很多种。为了确保本章提供了如何使用事件触发器组件的示例,我们将使用它编写一个拖放机制。要为库存面板和右下面板创建拖放机制,请完成以下步骤:
There are quite a few different ways to make a drag and drop mechanic. To ensure that this chapter provides an example of how to use the Event Trigger component, we will write a drag and drop mechanic utilizing it. To create a drag and drop mechanic for the Inventory Panel and Bottom Right Panel, complete the following steps:
使用 UnityEngine.UI;
using UnityEngine.UI;
公共游戏对象拖拽项;¶公共画布拖拽画布;
public GameObject dragItem;¶public Canvas dragCanvas;
图 8.20:主摄像头的组件
我选择创建一个附加到主摄像头而不是单个库存物品的脚本,以减少重复该脚本的需要。
Figure 8.20: The components of the Main Camera
I’ve chosen to create a script that attaches to the Main Camera rather than the individual inventory items to reduce the need to duplicate this script.
一旦完成后,它应该具有以下值:
Once that is done, it should have the following values:
图 8.21:Drag Canvas 上的 Canvas Scaler 组件
Figure 8.21: The Canvas Scaler component on the Drag Canvas
图 8.22:更新 Drag Canvas 的 Canvas 组件上的排序顺序
Figure 8.22: Updating the Sort Order on the Drag Canvas’ Canvas component
图 8.23:拖放组件
Figure 8.23: The Drag and Drop component
公共无效StartDrag(GameObject selectedObject){
dragItem = Instantiate(selectedObject,Input.mousePosition,selectedObject.transform.rotation)作为GameObject;
DragItem.transform.SetParent(dragCanvas.transform);
拖拽em.GetComponent<Image>().SetNativeSize();
拖动项目.transform.localScale = 1.1f * 拖动项目.transform.localScale;
}此功能将在拖动开始时调用。它接受 GameObject 作为参数,然后在鼠标位置创建它的新实例。然后移动它,使其成为dragCanvas的子项。最后,它将 Image 组件上的精灵的大小设置为原始大小。这会将 Image 的 Rect Transform 的比例重置为其精灵的原始像素大小。(有关设置原始大小的更多信息,请参阅第 12 章)。最后一行使图像比其原始尺寸大 10%。
public void StartDrag(GameObject selectedObject){
dragItem = Instantiate(selectedObject, Input.mousePosition, selectedObject.transform.rotation) as GameObject;
dragItem.transform.SetParent(dragCanvas.transform);
dragItem.GetComponent<Image>().SetNativeSize();
dragItem.transform.localScale = 1.1f * dragItem.transform.localScale;
}This function will be called when a drag begins. It accepts a GameObject as a parameter and then creates a new instance of it at the position of the mouse. It then moves it so that it is a child of dragCanvas. Lastly, it sets the size of the sprite on the Image component to native size. This resets the scale of the Image’s Rect Transform to its sprite’s original pixel size. (Refer to Chapter 12 for more on Set Native Size). The last line makes the image 10% bigger than its native size.
笔记
Note
在我们连接BeginDrag和Drag事件之后,如果你注释掉将大小设置为本机的代码行,你会看到图像实际上并没有在场景中渲染,因为它的比例与布局组内的原始游戏对象相比“古怪”。
After we hook up our BeginDrag and Drag events, if you comment out the line of code that sets the size to native, you›ll see that the Image does not actually render in the scene, because its scale is «wacky» from the original GameObject being within a Layout Group.
公共无效拖动(){
DragItem.transform.position = Input.mousePosition;
}当对象被拖拽时,该函数会被调用。对象被拖拽时,它会随鼠标保持位置不变。
public void Drag(){
dragItem.transform.position = Input.mousePosition;
}This function will be called when an object is being dragged. While the object is dragged, it will keep position with the mouse.
图 8.24:选择食物游戏对象
Figure 8.24: Selecting the Food GameObject
图 8.25:带有事件触发器组件的食物游戏对象
Figure 8.25: The Food GameObject with the Event Trigger component
图 8.26:具有两个事件的事件触发器组件
Figure 8.26: The Event Trigger component with two events
图 8.27:添加开始拖动事件
Figure 8.27: Adding a Begin Drag event
图 8.28:使用相机更新 Begin Drag 事件
Figure 8.28: Updating the Begin Drag event with the camera
图 8.29:添加 StartDrag 方法
完成此操作后,您将看到以下内容:
图 8.30:添加 StartDrag 方法
Figure 8.29: Adding the StartDrag method
Once you have done so, you should see the following:
Figure 8.30: Adding the StartDrag method
图 8.31:更新 StartDrag 方法
Figure 8.31: Updating the StartDrag method
图 8.32:添加 Drag 方法
Figure 8.32: Adding the Drag method
图 8.33:从库存中拖出橙子
你会看到,在在层次结构中,有一个名为Food(Clone)的新 GameObject,它是Drag Canvas的子对象。这是开始拖动时创建的橙子。
图 8.34:拖拽画布中被拖拽的项目
此时,您可以根据需要制作任意数量的克隆。不过,稍后我们将使Drag Canvas中一次只有一个克隆。
Figure 8.33: Dragging the orange from the inventory
You’ll see, in the Hierarchy, there is a new GameObject called Food(Clone) that is a child of the Drag Canvas. This is the orange that gets created when you begin dragging.
Figure 8.34: The item being dragged in the Drag Canvas
At this point, you can actually make as many of these clones as you want. In a moment, however, we will make it so that there is only one clone in the Drag Canvas at a time.
公共无效StopDrag(){
销毁(dragItem);
}一旦不再拖动,此代码就会销毁Food(Clone)游戏对象。
public void StopDrag(){
Destroy(dragItem);
}This code will destroy the Food(Clone) GameObject once it is no longer being dragged.
图 8.35:带有 Drag 方法的 End Drag 事件
Figure 8.35: The End Drag event with the Drag method
图 8.36:带有 StopDrag 方法的 End Drag 事件
Figure 8.36: The End Drag event with the StopDrag method
公共无效删除(图像dropSlot){
游戏对象 droppedItem = dragCanvas.transform.GetChild(0).gameObject;
dropSlot.sprite = dropItem.GetComponent<Image>().sprite;
}这函数接受一个Image作为参数。此 Image 将是接收放置的插槽的 Image 组件。函数的第一行找到 dragCanvas 的第一个子项(在位置0处),然后将其 Image 的精灵分配给dropSlot的精灵。由于我们已经设置了StopDrag()函数以在停止拖动后销毁被拖动的对象,因此我们不必担心Drag Canvas GameObject 有多个子项,这使其成为查找被拖动对象的最简单方法。
public void Drop(Image dropSlot){
GameObject droppedItem = dragCanvas.transform.GetChild(0).gameObject;
dropSlot.sprite = droppedItem.GetComponent<Image>().sprite;
}This function accepts an Image as a parameter. This Image will be the Image component of the slot that will receive a drop. The first line of the function finds the first child of the dragCanvas (at position 0) and then assigns its Image’s sprite to the sprite of the dropSlot. Since we have set up the StopDrag() function to destroy the object being dragged once it stops dragging, we don’t have to worry about there being more than one child of the Drag Canvas GameObject, making this the easiest way to find the object being dragged.
图 8.37:选择正确的食物
我们使用第二张食物图像,而不是第一张,因为第一张里面已经有一个橙子了,而且很难看出我们的脚本在那个位置起作用了。
Figure 8.37: Selecting the correct Food item
We’re using the second Food Image, rather than the first, because the first already has an orange in it, and it will be hard to tell that our script worked in that slot.
图 8.38:Drop 事件
Figure 8.38: The Drop event
图 8.39:Drop 事件及其方法
Figure 8.39: The Drop event with its method populated
图 8.40:具有正确参数的 Drop 事件
Figure 8.40: The Drop event with the correct parameter
DragItem.GetComponent<Image>().raycastTarget = false;
dragItem.GetComponent<Image>().raycastTarget = false;
复制我们将事件挂接到的库存面板中的食品项目的事件触发器组件,使用组件右上角的三个点。
Copy the Event Trigger component of the Food Item in the Inventory Panel that we hook the events up to, using the three dots in the component’s top-right corner.
这就是拖放代码。目前,暂停面板会阻止对库存面板内物品的射线投射,因此您不必担心在游戏暂停时禁用这些事件。但是,如果您最终要更改布局,则需要在执行任务之前检查ShowHidePanels中的pauseUp变量是否为false 。
That’s it for the drag and drop code. Currently, the Pause Panel blocks the raycast on the items within the Inventory Panel, so you don’t have to worry about disabling these events when the game is paused. However, if you end up changing the layout, you will want to do so by checking whether the pauseUp variable in ShowHidePanels is false before performing the tasks.
如果您想允许对象来回移动(从两个面板拖动并放入两个面板),您需要做的就是将相应的组件复制到相对的面板!
If you want to allow the objects to go back and forth (drag from both Panels and drop in both Panels), all you have to do is copy the appropriate component to the opposite Panels!
您可能还想将重复的 UI 元素制作成预制件,以便在开发过程中节省一些时间或以编程方式实例化它们。
You might also want to make the repeated UI elements prefabs so that you can save yourself some time during development or instantiate them programmatically.
本章中还有很多例子我想介绍,但我不能让这一章占据整本书的页数!您将在接下来的章节中看到更多关于如何使用事件系统的示例,所以不用担心;这不是您将看到的最后一个代码示例。
There are so many more examples I would love to cover in this chapter, but I can’t make this chapter take up the entire page count of the book! You’ll see more examples of how to use the Event System in the upcoming chapters, so don’t worry; this isn’t the last code example you will see.
最后本章中我想介绍的示例是如何使用双指触摸来平移相机,以及捏合来缩放。我们还将实现左键单击平移和滚轮缩放,以便您可以轻松地在计算机上测试它(当您没有多点触控输入时)。下图显示了我们将要实现的内容。
The last example I want to cover in this chapter is how to pan the camera with a two-finger touch and pinch to zoom. We will also implement a left-click pan and scroll wheel zoom so that you can easily test it on your computer (when you don’t have multi-touch input). The following image shows what we will be implementing.
图 8.41:平移和缩放代码工作示例
Figure 8.41: Demonstration of the pan and zoom code working
要实现相机的平移和缩放,请完成以下步骤:
To implement a pan and zoom on the camera, complete the following steps:
图 8.42:图块可能的精灵
Figure 8.42: The possible sprites for the tile
图 8.43:TileMaker.cs 脚本组件
这将使总共500 个图块在25列和20行中实例化。
Figure 8.43: The TileMaker.cs script component
This will make a total of 500 tiles instantiate in 25 columns and 20 rows.
图 8.44:场景中的宝石
Figure 8.44: The gems in the scene
图 8.45:CameraHandler.cs 脚本组件
Figure 8.45: The CameraHandler.cs script component
The various properties add bounds to how far the camera can pan, how much it can zoom, and how quickly it pans and zooms.
这段代码的大部分内容是矢量数学,我将留给您自己查看。我想重点介绍的代码部分是与本章相关的部分,特别是涉及Input和Touch 的部分。首先,让我们看一下HandleMouse()方法。我已突出显示相关部分:
无效 HandleMouse() {
如果(输入.GetMouseButtonDown(0)){
最后平移位置 =输入.鼠标位置;
} 否则,如果(输入.GetMouseButton(0)){
PanCamera(Input.mousePosition);
}
float scroll = Input.GetAxis("鼠标滚轮") ;
缩放相机(滚动,缩放速度鼠标);
}注意它使用Input.GetMouseButtonDown(0)来查看鼠标左键当前是否被按下,使用Input.GetMouseButton(0)来查看鼠标按钮是否已被单击,并使用Input.mousePosition来查找鼠标所在的位置。
A large portion of this code is vector math, and I will leave that for you to review on your own. The parts of this code I want to focus on are the parts relative to this chapter, specifically the parts involving Input and Touch. First, let’s look at the HandleMouse() method. I’ve highlighted the relevant parts:
void HandleMouse() {
if (Input.GetMouseButtonDown(0)) {
lastPanPosition = Input.mousePosition;
} else if (Input.GetMouseButton(0)) {
PanCamera(Input.mousePosition);
}
float scroll = Input.GetAxis("Mouse ScrollWheel");
ZoomCamera(scroll, zoomSpeedMouse);
}Notice that it uses Input.GetMouseButtonDown(0) to see if the left mouse button is currently held down, Input.GetMouseButton(0) to see if the mouse button has been clicked, and Input.mousePosition to find where the mouse is located.
开关(输入.触摸次数){Input.touchCount返回当前有多少根手指触摸屏幕。
switch(Input.touchCount) {Input.touchCount returns how many fingers are currently touching the screen.
触摸 touch = Input.GetTouch(0);
如果 ( touch.phase == TouchPhase.Began ) {
lastPanPosition = touch.position;
panFingerId = touch.fingerId;
} 否则,如果 ( touch.fingerId == panFingerId && touch.phase == TouchPhase.Moved ) {
平移相机(触摸.位置);
}我再次强调了相关代码。
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began) {
lastPanPosition = touch.position;
panFingerId = touch.fingerId;
} else if (touch.fingerId == panFingerId && touch.phase == TouchPhase.Moved) {
PanCamera(touch.position);
}Once again, I have highlighted the relevant code.
Vector2[] newPositions = new Vector2[]{ Input.GetTouch(0).position, Input.GetTouch(1).position};它将第一根手指和第二根手指的位置存储在Vector2数组中。然后使用一些复杂的矢量数学来判断手指是相互靠近还是相互远离,从而产生捏合缩放效果。
Vector2[] newPositions = new Vector2[]{Input.GetTouch(0).position, Input.GetTouch(1).position};It stores the position of the first finger and the second finger in a Vector2 array. It then uses some fancy vector math to see whether the fingers are getting closer to each other or further away from each other, creating a pinch-to-zoom effect.
Vector3 偏移量=相机.ScreenToViewportPoint (上次平移位置 - 新平移位置);
这行代码至关重要,因为它将鼠标或手指的屏幕坐标转换为视口的坐标。
Vector3 offset = theCamera.ScreenToViewportPoint(lastPanPosition - newPanPosition);
This line of code is crucial as it converts the screen coordinates of your mouse or finger to that of the viewport.
图 8.46:背景画布上的事件触发器
Figure 8.46: The Event Trigger on the Background Canvas
将以下变量添加到ShowHidePanels.cs类:
相机处理器 相机处理器;
Add the following variable to the ShowHidePanels.cs class:
CameraHandler cameraHandler;
void Awake() {¶ cameraHandler = GetComponent<CameraHandler>();¶}void Awake() {¶ cameraHandler = GetComponent<CameraHandler>();¶}如果(inventoryUp || pauseUp)
{
相机处理程序.关闭平移和变焦();
}
别的
{
cameraHandler.TurnOnPanAndZoom();
}if (inventoryUp || pauseUp)
{
cameraHandler.TurnOffPanAndZoom();
}
else
{
cameraHandler.TurnOnPanAndZoom();
}你应该现在具有功能齐全的平移和缩放功能,当菜单可见时将被禁用!
You should now have a fully functional pan and zoom that are disabled when menus are visible!
这几乎是我在游戏 Barkeology 中使用的确切代码,您可以在 iOS 应用商店中找到:https ://apps.apple.com/kn/app/barkeology/id1500348850因此,如果您无法在移动设备上测试代码,但想查看其运行情况,您可以在那里查看它。
This is almost the exact code I used in my game Barkeology, which you can find on the iOS app store: https://apps.apple.com/kn/app/barkeology/id1500348850 So, if you do not have the ability to test your code on your mobile device, but would like to see it in action, you can view it there.
虽然这标志着本章的结束,但我们将在本文中继续研究事件系统,因此您将看到更多示例。
While this marks the end of the chapter, we will continue to work in the Event System throughout this text, so you will see plenty more examples.
现在我们知道如何利用事件系统和为 UI 元素编程,我们可以开始制作交互式和可视化的 UI 元素。我们还可以创建当事件发生时其各种属性会发生变化的 UI。
Now that we know how to utilize the Event System and program for UI elements, we can start making interactive and visual UI elements. We can also create UI that has its various properties change when events occur.
我们在本章中讲了很多内容!我们讨论了如何访问 UI 元素的属性以及如何使用事件系统。我们还讨论了如何使用输入模块。现在,您可以创建响应用户输入的 UI 以及响应游戏内事件的 UI。
We covered a lot in this chapter! We discussed how to access the properties of UI elements and how to work with the Event System. We also discussed how to use the Input Module. Now, you can create UI that responds to user inputs as well as UI that responds to events within your game.
在下一章中,我们将了解 Unity 提供的另一个输入系统:新输入系统(是的,这是它的实际名称)。
In the next chapter, we will look at the other input system provided by Unity: the New Input System (yeah, that’s its actual name).
在本部分中,您将探索 uGUI 系统提供的各个可交互组件。本部分将介绍如何布局和编程按钮。此外,还将介绍两种显示文本的方式:UI Text 和 Text-TextMeshPro。您将学习如何使用 UI 图像并为其添加各种效果。本部分将讨论如何使用遮罩、滚动条和滚动视图,以便您可以制作可扩展的 UI 菜单。最后,您将学习如何使用 Unity UI 系统提供的所有其他可交互 UI 组件。
In this part, you will explore the individual interactable components provided by the uGUI system. How to lay out and program for buttons is covered. Additionally, the two ways in which you can display text, UI Text and Text-TextMeshPro are covered. You will learn how to work with UI Images and add various effects to them. How to use masks, scrollbars, and scroll views is discussed so that you can make expandable UI menus. And lastly, you’ll learn how to use all the other Interactable UI Components provided by the Unity UI system.
本部分包含以下章节:
This part has the following chapters:
Unity 的 UI 系统提供的按钮是图形对象,它们预先利用了我们在上一章中介绍的事件系统。当按钮被放置在场景中时,它会自动添加允许玩家与之交互的组件。这是有道理的,因为按钮的全部意义在于与它交互。让我们探索如何在游戏中添加和使用按钮。
Buttons provided by Unity’s UI system are graphical objects that preutilize the Event System we covered in the last chapter. When a Button is placed in a scene, it automatically has components added to it that allow the player to interact with it. This makes sense because the whole point of a Button is to interact with it. Let’s explore how to add and utilize buttons in our games.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
笔记
Note
示例部分之前的部分中显示的所有示例都可以在代码包中提供的 Unity 项目中找到。它们可以在标记为Chapter9 的场景中找到。
All the examples shown in the sections before the Examples section can be found within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter9.
每个示例图像都有一个标题,注明场景中的示例编号。
Each example image has a caption stating the example number within the scene.
在场景中,每个示例都在其自己的画布上,并且一些画布处于停用状态。要查看停用画布上的示例,只需在检查器中选中画布名称旁边的复选框即可。每个画布还具有自己的事件系统。如果您一次激活多个画布,这将导致错误。
In the scene, each example is on its own Canvas, and some of the Canvases are deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector. Each Canvas is also given its own Event System. This will cause errors if you have more than one Canvas activated at a time.
您可以在此处找到本章节的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2009
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2009
按钮是 UI 对象,需要玩家点击。您可以通过选择+ | UI | Button来创建按钮。当您创建按钮时,带有Text子对象的Button对象将被放置在场景中。与所有其他 UI 对象一样,如果创建按钮时场景中没有 Canvas 或 Event System ,则会为您创建一个Canvas和Event System ,其中Canvas是新按钮的父级:
Buttons are UI objects that expect a click from the player. You can create a Button by selecting + | UI | Button. When you make a button, a Button object with a Text child will be placed in the scene. As with all other UI objects, if no Canvas or Event System is in the scene when you create the Button, a Canvas and Event System will be created for you, with the Canvas being a parent of your new Button:
图 9.1:向场景中添加新的 UI 按钮
Figure 9.1: Adding a new UI Button to the scene
如果您不想在按钮上显示文本,您可以删除子文本对象。
You can delete the child Text object if you do not want to have text displaying on your Button.
Button 对象有三个主要组件:Rect Transform(与所有其他 UI 图形对象一样)、Image组件和Button组件:
The Button object has three main components: Rect Transform (like all other UI graphical objects), an Image component, and a Button component:
图 9.2:新 UI 按钮的组件
Figure 9.2: The components of a new UI Button
我们将在下一章中更深入地讨论图像组件,但现在,只需知道图像组件确定按钮在标准状态下的外观。
We’ll discuss the Image component more thoroughly in the next chapter, but for now, just know that the Image component determines the look of the Button in its standard state.
Button组件提供了所有允许玩家与按钮交互的属性,并确定当玩家尝试与按钮交互时按钮将执行的操作。
The Button component provides all the properties that allow the player to interact with the button and determine what the Button will do when the player attempts to interact with it.
Button组件的第一个属性是Interactable属性。此属性决定按钮是否可以通过接受玩家的输入进行交互。默认情况下,此功能处于打开状态,但如果您想禁用按钮,可以将其关闭。
The first property of the Button component is the Interactable property. This property determines whether the Button can or cannot be interacted with by accepting input from the player. This is turned on by default but can be turned off if you want to disable the button.
您将看到按钮组件已附加了单击事件。当玩家在鼠标悬停在按钮上时单击并释放鼠标时,将触发单击事件。如果玩家单击按钮,将鼠标移到按钮的 Rect Transform 之外,然后释放鼠标,则按钮的单击事件将不会注册。您可以按照第 8 章中设置事件的方式设置单击事件。
You’ll see that the Button component already has an On Click Event attached to it. The On Click Event triggers when the player clicks and releases the mouse while hovering over the button. If the player clicks on the Button, moves the mouse outside of the Button’s Rect Transform, and then releases the mouse, the Button’s On Click Event will not register. You set the On Click Event the same way we set Events in Chapter 8.
Button组件的第二个属性是Transition属性。Transition属性决定按钮处于不同的状态。这些不同的状态不是突出显示(或正常)、突出显示、按下、选中或禁用。这些转换是自动执行的,不需要编码。
The second property of the Button component is the Transition property. The Transition property determines the way the button will visually react when the button is in different states. These different states are not highlighted (or normal), highlighted, pressed, selected, or disabled. These transitions are performed automatically and do not require coding.
你可以指定四种不同类型的过渡:无、色调、精灵交换和动画n .
There are four different types of transitions you can assign: None, Color Tint, Sprite Swap, and Animation.
选择“无”作为过渡类型将意味着按钮不会因不同的状态而发生视觉变化西。
Selecting None for the Transition type would mean that the button will not visually change for the different states.
颜色色调过渡类型将使按钮根据其状态改变颜色。您指定正常颜色、突出显示颜色、按下颜色、选定颜色和禁用颜色。
The Color Tint transition type will make the button change color based on its state. You assign Normal Color, Highlighted Color, Pressed Color, Selected Color, and Disabled Color.
在下面的例子中,您可以看到当鼠标悬停在按钮上时,按钮变为绿色(因此突出显示),而当鼠标按下按钮时,按钮变为红色:
In the following example, you can see that the button changes to green when the mouse is hovering over it (hence, highlighting it) and turns red as the mouse is being pressed down on it:
图 9.3:第 9 章场景中的颜色交换示例
Figure 9.3: Color Swap Example in the Chapter9 scene
如果您查看前面的示例,您会注意到单击按钮后按钮会变成黄色。这是因为它已被选中。要将其恢复为正常颜色,请单击按钮外部的任意区域。
If you view the preceding example, you’ll notice that the button turns yellow after it has been clicked. This is because it is selected. To return it to the normal color, click on any area outside of the button.
要使按钮进入赋予其“禁用颜色”的状态,必须禁用按钮的“可交互”属性。在以下屏幕截图中,您将看到在打开和关闭“可交互”属性时按钮如何变化:
For a Button to enter the state that will give it its Disabled Color, the Button’s Interactable property must be disabled. In the following screenshot, you will see how the button changes when the Interactable property is toggled on and off:
图 9.4:第 9 章场景中的禁用按钮示例
Figure 9.4: Disabled Button Example in the Chapter9 scene
您还可以选择目标图形。这是将接收过渡的图形。默认情况下,它被分配给按钮本身,因此按钮的图像在突出显示、按下或禁用时会改变颜色。但是,您可以选择将辅助图形设为目标图形。这意味着分配给目标图形的项目将改变颜色根据按钮的交互。在以下示例中,将辅助图像指定为目标图形。您会看到按钮没有经历过渡;相反,星形图像经历了过渡:
You also can select the Target Graphic. This is the graphic that will receive the transitions. By default, it is assigned to the Button itself, so the Button’s image will change color when it is highlighted, pressed, or disabled. However, you can choose to make a secondary graphic the Target Graphic. This means the item assigned to the Target Graphic will change color based on the interaction of the button. In the following example, a secondary image is assigned as the Target Graphic. You’ll see the button does not undergo transitions; instead, the star image undergoes transitions:
图 9.5:第 9 章场景中的目标图形示例
Figure 9.5: Target Graphic Example in the Chapter9 scene
需要注意的是,这些颜色将为目标图形的图像着色。因此,它本质上是在目标图形的顶部放置了一个颜色叠加层。如果目标图形的图像是黑色,这些色调似乎不会对图像产生任何影响。请注意,默认的正常颜色是白色。在图像上添加白色色调不会改变图像的颜色。
It’s important to note that these colors will tint the Target Graphic’s image. So, it essentially puts a color overlay on top of the Target Graphic. If the image of the Target Graphic is black, these tints will not appear to have any effect on the image. Note that the default Normal Color is white. Putting a white tint on an image does not change the color of the image.
颜色乘数属性允许使颜色更明亮或增加图形的 alpha 值。因此,如果图形的 alpha 值小于1,则此属性将增加图形的 alpha 值。这适用于所有(包括正常)状态。
The Color Multiplier property allows you to brighten up the colors or increase the alpha of the graphic. So, if the graphic has an alpha value that is less than 1, this property will increase the alpha of the graphic. This applies to all (including the normal) states.
淡入淡出持续时间属性是状态颜色之间淡入淡出所需的时间(以秒为单位)卢比
The Fade Duration property is the time (in seconds) it takes to fade between the state colors.
Sprite Swap过渡类型将使按钮针对不同的状态改变为不同的图像。
The Sprite Swap transition type will make the button change to different images for different states.
你会注意到没有属性为正常状态分配精灵。这是因为正常状态将只使用分配给图像组件的精灵。
You’ll note that there is no property to assign a sprite for the normal state. This is because the normal state will just use the sprite assigned to the Image component.
我们在第 6 章中导入的精灵表有四个按钮图像,这将有助于演示精灵交换过渡:标记为uiElements_39、uiElements_40、uiElements_41和uiElements_42的图像(如下面的屏幕截图所示):
The sprite sheet we imported in Chapter 6, has four button images that will be helpful in demonstrating the Sprite Swap Transition: the images labeled uiElements_39, uiElements_40, uiElements_41, and uiElements_42 (as shown in the following screenshot):
图 9.6:我们将用来演示按钮精灵交换的精灵
Figure 9.6: The sprites we will use to demonstrate button Sprite Swap
为了让按钮在适当的状态下显示这些图像,我们只需将uiElements_39分配给Image组件上的Source Image,将uiElements_40分配给Highlighted Sprite,将uiElements_41分配给Pressed Sprite,将uiElements_39分配给Selected Sprite,将uiElements_42分配给Disabled Sprite。我们还需要从按钮中删除子Text对象:
To have the button take on these images at the appropriate states, we simply need to assign uiElements_39 to the Source Image on the Image component, uiElements_40 to the Highlighted Sprite, uiElements_41 to the Pressed Sprite, uiElements_39 to the Selected Sprite, and uiElements_42 to the Disabled Sprite. We also need to delete the child Text object from the button:
图 9.7:第 9 章场景中的精灵交换示例
Figure 9.7: Sprite Swap Example in the Chapter9 scene
请记住,您可以查看通过取消选择Interactable来禁用Sprite 。
Remember, you can view the Disabled Sprite by deselecting Interactable.
我一直觉得一个很棒的精灵交换动画很有吸引力,就是将一个按钮的图像应用到按下的图像上,这个按钮看起来是按下的。例如,我把左边的按钮稍微编辑了一下,就创建了右边的按钮。变化很小,但我通过稍微向下移动按钮的顶部来改变它,让它看起来是按下的:
A nice sprite swap animation that I always find appealing is applying an image of a button that appears down-pressed to the pressed image. For example, I took the button on the left and slightly edited it to create the button on the right. The change is slight, but I changed it by slightly moving down the top part of the button to make it look pressed:
图 9.8:缩进的按钮动画表
Figure 9.8: The indented button animation sheet
并排查看时看起来没有太大区别。但是,当左侧图像用于源图像、突出显示的 Sprite、选定的 Sprite和禁用的 Sprite,而右侧图像用于按下的 Sprite时,按钮会过渡到显示非常漂亮的按钮按下动画:
It doesn’t look like much of a difference when viewed side by side. However, when the left-hand image is used for Source Image, Highlighted Sprite, Selected Sprite, and Disabled Sprite, and the right-hand image is used for Pressed Sprite, the button transitions to show a very nice button-pressing animation:
图 9.9:第 9 章场景中的按下按钮示例
Figure 9.9: Pressed Button Example in the Chapter9 scene
要查看实际效果,请查看Chapter9Scene中的“按下按钮示例画布”;它确实点击起来非常令人满意。
To see this in action, view Pressed Button Example Canvas in Chapter9Scene; it really is quite satisfying to click.
在第 11 章中,我们将探索如何在没有按钮过渡属性的情况下创建图像交换,例如静音/取消静音按钮n.
In Chapter 11, we will explore how to create an image swap without the button transition property, for something like a mute/unmute button.
动画转换允许按钮在其各种状态下进行动画处理。
The Animation transition allows the button to animate in its various states.
动画过渡类型需要将动画组件附加到按钮上。它可以通过将一组预先存在的动画拖到按钮的检查器上来添加到按钮,或者您可以通过选择自动生成动画来创建一个全新的动画控制器。如果您使用预先存在的动画控制器,您可以简单地将动画分配给各个状态。但是,如果您生成新的动画控制器,则可以从动画窗口中的剪辑列表中选择状态,然后从该窗口中编辑它们。本章的示例部分提供了制作具有动画过渡的按钮的示例呃。
Animation transition types require an Animator component attached to the Button. It can add a preexisting set of animations to a Button by dragging it onto the Button’s Inspector, or you can make a whole new Animator Controller by selecting Auto Generate Animation. If you use a preexisting Animator Controller, you can simply assign the Animations to the individual states. However, if you generate a new Animator Controller, you can select the state from the list of Clips in the Animation window and edit them from that window. An example of making a Button with animated transitions is provided in the Examples section of this chapter.
按钮具有导航属性,用于确定通过键盘或控制器输入突出显示的顺序:
Buttons have a Navigation property that determines the order in which they will be highlighted via keyboard or controller inputs:
图 9.10:按钮组件上的导航属性
Figure 9.10: The Navigation property on the Button component
如果要导航到所有按钮,则场景中每个按钮都必须设置此属性。如果您回忆一下第 8 章,我们讨论了事件系统组件的First Selected属性。如果您为某个按钮分配了First Selected属性,则加载场景时该按钮将突出显示。如果您随后使用键盘浏览按钮,则导航将从具有First Selected属性的按钮开始。但是,如果您没有为某个按钮分配First Selected属性,则在使用鼠标选择了某个按钮之前,导航不会开始。下一个选择的按钮由您为按钮选择的导航选项决定。
Each Button within the scene must have this property set if you want to navigate to all the Buttons. If you recall from Chapter 8, we discussed the First Selected property of the Event System component. If you have a Button assigned to First Selected, that Button will be highlighted when you load the scene. If you then navigate through the Buttons with the keyboard, the navigation will begin at the Button with the First Selected property. However, if you do not have a Button assigned as First Selected, navigation will not start until a button has been selected with the mouse. The next button selected is determined by the navigation option you have selected for the buttons.
有五种导航选项:无、水平、垂直、自动和明确。
There are five Navigation options: None, Horizontal, Vertical, Automatic, and Explicit.
选择“无”将禁用所有键盘导航到指定的按钮。请记住,这是针对单个按钮的,因此如果您想禁用所有键盘导航,则必须为所有按钮选择“无” 。
Selecting None will disable all keyboard navigation to the specified Button. Remember that this is for the individual button, so if you want to disable all keyboard navigation, you must select None for all Buttons.
水平和垂直的解释非常直观。如果按钮的导航属性设置为水平,则在选择按钮时,将水平选择下一个选定的按钮,即使用左右箭头。垂直的工作原理类似;这表示从该按钮导航,而不是导航到该按钮。因此,如果设置了水平的按钮具有导航属性,您仍然可以从另一个具有垂直按钮的按钮访问该按钮。
Horizontal and Vertical are pretty self-explanatory. If a Button has its Navigation property set to Horizontal, when it is selected, the next button selected will be chosen horizontally, meaning with the right and left arrows. Vertical works similarly; this represents the navigation away from that button, not to that button. So, if a button that has Horizontal set has its navigation property, you can still access that button from another with a vertical button.
自动将允许按钮水平和垂直导航,由其相对于其他按钮的位置自动确定。
Automatic will allow the Button to navigate both Horizontally and Vertically, as determined automatically by its position relative to the other buttons.
可视化按钮允许您查看导航设置的可视化表示。每个按钮将连接在一起,箭头表示将在其之后选择哪个按钮。每个箭头从按钮的侧面开始,以象征按下的方向箭头,并指向按下该箭头时将突出显示的下一个按钮。对于例如,如果箭头从按钮的右侧开始,则该箭头表示如果玩家按下键盘上的右键,下一个将被选择的按钮。以下示例显示了五个按钮的可视化,所有按钮的导航属性均设置为“自动”:
The Visualize button allows you to see a visual representation of the navigation setup. Each Button will be connected, with arrows demonstrating which Button will be selected after it. Each arrow begins on the side of the button to symbolize the directional arrow pressed, and it points at the next button that will be highlighted if that arrow is pressed. For example, if an arrow begins on a Button’s right, that arrow symbolizes what Button will be selected next if the player presses right on the keyboard. The following example shows the visualization of five Buttons, all with their Navigation property set to Automatic:
图 9.11:第 9 章场景中的导航示例
Figure 9.11: Navigation Example in the Chapter9 scene
在上面的示例中,每个按钮都有一个颜色色调过渡属性,其中突出显示颜色和选定颜色均指定为绿色。标记为1 的按钮已在事件系统中指定为首次选定:
In the preceding example, each Button has a Color Tint Transition property with the Highlighted Color and Selected Color assigned to green. The Button labeled 1 has been assigned as First Selected in the Event System:
图 9.12:按钮 1 被指定为第一个选定按钮的事件系统
Figure 9.12: Event System with Button 1 assigned to First Selected
因此,当场景开始播放时,它将自动突出显示。根据可视化图表,如果在场景加载后在键盘上选择右箭头键,则将选择标有2的按钮:
Therefore, when the scene begins playing, it will automatically be highlighted. Based on the visualized graph, if the right arrow key is selected on the keyboard after the scene loads, the Button labeled 2 will be selected:
图 9.13:第 9 章场景中的导航示例
Figure 9.13: Navigation Example in the Chapter9 scene
最后一种导航类型是显式导航,可以实现更好的控制。通过这种方式,您可以明确定义每次按下键盘时将访问哪个按钮。
The last Navigation type is Explicit and allows for significantly better control. With this, you can explicitly define which button will be accessed with each individual keyboard press.
图 9.14:显式导航类型的属性
Figure 9.14: The properties of the Explicit Navigation type
假设您希望玩家按 1-2-3-4-5 的顺序循环按钮,然后循环回到 1。您希望使用向上按钮或向右按钮来实现这一点。前面提到的任何导航方法都不允许这样做。但是,您可以使用显式导航类型来实现这一点。本章的第一个分步示例介绍了如何创建明确的按钮导航图。
Let’s say that you wanted the player to cycle through the buttons in the order 1-2-3-4-5 and then loop back to 1. You want this to happen with either the up button or the right button. None of the previously mentioned navigation methods will allow that. However, you can achieve that with the Explicit Navigation type. The first step-by-step example of this chapter covers how to create an explicit button navigation map.
在第 2 章中,我们讨论了屏幕上的点击区域。在手机游戏中,点击屏幕上的任意位置通常都会导致事件。例如,很多时候当您选择弹出窗口之外的区域时,它会关闭。其他示例是,您可以点击屏幕的左侧或右侧来前后移动角色。点击屏幕区域可能看起来不像按钮实现,但实际上就是这样!按钮只是不可见的。
In Chapter 2, we discussed tapping zones on the screen. Often, in mobile games, tapping anywhere on the screen will cause an event. For example, many times when you select outside of a pop-up window, it will close. Other examples are when you can tap on the left or right side of the screen to move a character back and forth. Tapping on areas of the screen may not seem like a button implementation, but it actually is! The buttons are just invisible.
让我们探索第一个场景。如果您查看第 9 章场景中的“关闭面板示例”游戏对象,您将看到一个面板,当按下信息按钮时会出现,当按下关闭按钮时会关闭,当按下面板外部的区域时也会关闭。这是通过在面板后面放置一个大的隐形按钮来实现的。为了使其正常工作,它需要位于信息按钮的前面(阻止射线投射到它)。
Let’s explore the first scenario. If you review the Close Panel Example GameObject of the Chapter9 scene, you will see a Panel that appears when an info button is pressed, closes when the close button is pressed, and also closes when the area outside of the Panel is pressed. This is accomplished by putting a large, invisible button behind the Panel. For it to work appropriately, it needs to be in front of the info button (blocking raycast to it).
图 9.15:第 9 章场景中的关闭面板示例
Figure 9.15: Close Panel Example in the Chapter9 scene
即使背景区域会关闭面板,包含关闭按钮也是一个很好的设计选择。很多人并不直观地认为点击面板外部是关闭面板的操作,如果您不提供关闭按钮,用户将会花时间寻找它。
It’s a good design choice to include the close button, even if the background area will dismiss the Panel. Many people do not intuitively consider tapping outside a Panel to be an action that will close it and will spend time looking for the close button if you do not provide it.
现在让我们探讨第二种情况,即屏幕的两侧会引起不同的动作。在Tap Zone 示例 GameObject中,点击左侧会使拐杖糖向左移动,点击右侧会使拐杖糖向右移动。再次使用大型隐形按钮。
Now let’s explore the second scenario, where the two sides of the screen cause different actions. In the Tap Zone Example GameObject, tapping the left side moves the candy cane to the left, and tapping the right side moves the candy cane to the right. Once again, large invisible buttons are being utilized.
图 9.16:第 9 章场景中的点击区域示例
Figure 9.16: Tap Zone Example in the Chapter9 scene
当使用这些大型隐形按钮,考虑它们如何影响光线投射非常重要,即使它们是隐形的。它们会阻挡身后的东西!
When using these large invisible buttons, it is very important that you consider how raycast will be affected by them, even though they are invisible. They will block things behind them!
对于本章的前三个示例,我们将暂时离开我们一直在处理的场景,构建一个新场景,以便我们试验按钮导航和场景加载。然后,我们将从第 8 章的场景中继续,为我们的场景添加一些按钮不。
For the first three examples in this chapter, we will momentarily step away from the scene we have been working on to build a new scene that will allow us to experiment with button navigation and scene loading. We’ll then pick up where we left off with our scene from Chapter 8, to add some buttons to our scene.
We’ll build out a faux start screen that appears as follows:
图 9.17:我们将要构建的开始屏幕场景
Figure 9.17: The start screen scene we will build
这些按钮大部分都是虚拟按钮,但我们将在下一个示例中设置播放按钮来加载我们一直在处理的场景。
Most of these buttons will be dummy buttons, but we will set up the Play button in the next example to load the scene we have been working on.
让我们有能力进行实验按钮导航,我们将为其分配一个显式导航方案,以便我们可以按照以下模式循环浏览按钮:
To give us the ability to experiment with button navigation, we’ll assign an Explicit navigation scheme to it so that we can cycle through the buttons with the following pattern:
图 9.18:按钮导航图
Figure 9.18: The button navigation map
首先,播放按钮将是选中。连续按下键盘上的向下键将导致以下选择路径:
First, the Play button will be selected. Pressing the down key on the keyboard continuously will result in the following selection path:
图 9.19:带有向下箭头的按钮导航流程
Figure 9.19: The button navigation flow with the down arrow
Pressing the up button continuously will result in the following path:
图 9.20:带有向上箭头的按钮导航流程
Figure 9.20: The button navigation flow with the up arrow
Next, we’ll learn how to lay out the buttons.
Let’s start by creating a new scene and laying out the buttons.
要制作如图 9.17所示的模拟开始屏幕,请完成以下步骤:
To make a faux start screen as shown in Figure 9.17, complete the following steps:
从项目文件夹视图中,将Chapter8-Examples场景拖到Hierarchy中。你现在应该看到以下内容:
From the Project folder view, drag the Chapter8-Examples scene to the Hierarchy. You should now see the following:
图 9.21:同时加载两个场景
Figure 9.21: Loading the two scenes simultaneously
图 9.22:复制背景画布
Figure 9.22: Duplicating Background Canvas
图 9.23:删除场景
现在,层次结构中应该只有Chapter9-Examples-StartScreen,并且背景画布应该在场景中可见。
Figure 9.23: Removing the Scene
You should now have only Chapter9-Examples-StartScreen in the Hierarchy, and the Background Canvas should be visible in the scene.
笔记
Note
你会注意到,这样做后,场景中会有一个Canvas,但没有Event System。不过没关系;一旦我们添加新的 UI 元素,就会为我们添加一个 Event System 。
You’ll note that by doing this, we have a Canvas in the scene without an Event System. That’s okay, though; once we add new UI elements, an Event System will be added in for us.
系统会显示一条警告消息也会出现在Background Canvas的Canvas组件上:
图 9.24:Canvas 组件错误消息
要解决这个问题,只需将主摄像机从当前场景拖到渲染摄像机槽,并将排序层 设置为背景。
图 9.25:已分配主摄像头的 Canvas 组件
A warning message will also appear on the Canvas component of the Background Canvas:
Figure 9.24: The Canvas component error message
To fix this, simply drag the Main Camera from the current scene to the Render Camera slot and set the Sorting Layer to Background.
Figure 9.25: The Canvas component with the Main Camera assigned
图 9.26:播放按钮上的属性
Figure 9.26: The properties on the Play Button
图 9.27:播放按钮文本的属性
Figure 9.27: The properties on the Play Button’s text
图 9.28:开始屏幕按钮的布局
图 9.29:三个按钮的属性
Figure 9.28: The layout of the start screen buttons
To achieve the preceding layout, use the following properties and make sure to remove their child Text objects:
Figure 9.29: The properties of the three buttons
图 9.30:两个按钮的属性
Figure 9.30: The properties on the two buttons
Your scene should be correctly laid out now, so let’s work on setting up the navigation.
如果您选择任意在您的 Button 的Button组件中,您应该看到以下导航路径:
If you select the Visualize button on any of your Button’s Button components, you should see the following navigation path:
图 9.31:自动按钮导航可视化
Figure 9.31: The automatic button navigation visualization
这种导航设置比我在本文开头描述的设置允许的导航范围要大得多例如。这是因为每个按钮的导航默认设置为“自动” ,而“自动”允许多向导航。让我们让导航更线性一些,并将“播放按钮”设为事件系统中的第一个选定按钮。
This navigation setup allows significantly more navigation range than the setup I described at the beginning of this example. This is because each button has its navigation set to Automatic by default, and Automatic allows multidirectional navigation. Let’s make our navigation a bit more linear and make our Play Button the First Selected Button in our Event System.
要设置本示例开头描述的导航,请完成以下步骤:
To set up the navigation described at the beginning of this example, complete the following steps:
图 9.32:首先选择播放按钮的事件系统
现在,当我们开始循环按钮时,我们的导航将从播放按钮开始。此外,如果我们在场景加载时按下Enter键,播放按钮将自动执行。
Figure 9.32: The Event System with the Play Button as First Selected
Now, when we start cycling through our buttons, our navigation will begin at the Play Button. Also, if we were to hit the Enter key when this scene loads, the Play Button will automatically be executed.
图 9.33:带有颜色色调过渡的按钮组件
如果你玩游戏,你应该看到播放按钮变成红色,表示它已被选中(因为我们在步骤 1中将其设置为“首先选中” ):
图 9.34:播放按钮被选中并显示为红色
Figure 9.33: The Button component with Color Tint transitions
If you play the game, you should see the Play Button colored red, symbolizing that it is selected (since we set it as First Selected in Step 1):
Figure 9.34: Play Button selected and colored red
根据图 9.18至9.20,按下向上键时,播放按钮应导航至Twitter 按钮,按下向下键时,播放按钮应导航至成就按钮。因此,从层次结构中将这些按钮分别拖放到标有“向上选择”和“向下选择”的插槽中。
According to Figures 9.18 through 9.20, the Play Button should navigate to Twitter Button when the up key is pressed and Achievements Button when the down key is pressed. So, drag and drop those Buttons into the slots labeled Select On Up and Select On Down, respectively, from the Hierarchy.
If you have the Visualize button selected, as you are dragging the Buttons into their slots, you should see the navigation visualization starting to build out.
图 9.35:播放按钮的导航属性
Figure 9.35: The Navigation properties of the Play Button
笔记
Note
请记住,如果箭头从按钮顶部开始,则该箭头表示按下向上键时导航将到达的位置,如果箭头从按钮底部开始,则该箭头表示按下向下键时导航将到达的位置。
Remember that if an arrow begins on top of a Button, that arrow symbolizes where navigation will go if the up key is pressed, and if the arrow begins on the bottom of a Button, it symbolizes where navigation will go if the down key is pressed.
要看到成就按钮被选中,您实际上必须停止游戏并重新播放,因为我们没有设置Twitter 按钮返回播放按钮的导航。因此,停止游戏,再次按播放,然后按下向下键,您应该会看到成就按钮 突出显示为红色。
To see the Achievement Button become selected, you actually have to stop playing the game and replay, because we have not set up the navigation for the Twitter Button to go back to the Play Button. So, stop the game, press Play again, then press the down key, and you should see the Achievement Button highlight red.
笔记
Note
您无需重新启动游戏,也可以用鼠标突出显示“播放”按钮,然后导航到“成就”按钮。
Instead of restarting the game, you can also highlight the Play Button with your mouse so that you can then navigate to the Achievement Button.
|
按钮 Button |
选择向上 Select On Up |
选择向下 Select On Down |
|
播放按钮 Play Button |
Twitter 按钮 Twitter Button |
成就按钮 Achievement Button |
|
成就按钮 Achievement Button |
播放按钮 Play Button |
排行榜按钮 Leaderboard Button |
|
排行榜按钮 Leaderboard Button |
成就按钮 Achievement Button |
信息按钮 Info Button |
|
信息按钮 Info Button |
排行榜按钮 LeaderBoard Button |
Facebook 按钮 Facebook Button |
|
Facebook 按钮 Facebook Button |
信息按钮 Info Button |
Twitter 按钮 Twitter Button |
|
Twitter 按钮 Twitter Button |
Facebook 按钮 Facebook Button |
播放按钮 Play Button |
表 9.1:每个按钮的“向上选择”和“向下选择”分配
Table 9.1: The Select On Up and Select On Down assignments for each Button
完成后,您的导航可视化效果应如下所示:
When you are done, your navigation visualization should look like the following:
图 9.36:最终的导航流程可视化
Figure 9.36: The finalized Navigation flow visualization
如果您玩游戏,您应该能够使用箭头键轻松地循环按钮。
If you play your game, you should be able to easily cycle through the Buttons using the arrow keys.
我建议设置所有按钮具有水平或垂直 导航模式,并查看它们与我们创建的不同之处,以便您可以看到,通过将预定义模式应用于所有按钮,此模式是无法实现的。
I recommend setting all the buttons to have Horizontal or Vertical Navigation patterns and seeing how they differ from what we have created so that you can see that this pattern is not attainable with a predefined pattern applied to all the Buttons.
现在我们已经布置好了开始屏幕现在,让我们连接播放按钮来玩游戏。首先,使用Ctrl + D复制您在第 8 章中创建的场景,名为Chapter8-Examples。新场景应称为Chapter9-Examples。我们的目标是让Chapter9-Examples-StartScreen中的播放按钮加载Chapter9-Examples场景。
Now that we have our start screen laid out, let’s hook up the Play Button to play our game. First, duplicate the scene you created in Chapter 8, called Chapter8-Examples, using Ctrl + D. The new scene should be called Chapter9-Examples. Our goal is to have the Play Button in Chapter9-Examples-StartScreen load the Chapter9-Examples scene.
为了制作Chapter9-Examples-StartScreen场景中的播放按钮,请加载Chapter9-Examples场景并完成以下步骤:
To make the Play Button in the Chapter9-Examples-StartScreen scene, load up the Chapter9-Examples scene and complete the following steps:
图 9.37:构建设置中没有场景
Figure 9.37: The Build Settings with no scenes in the build
图 9.38:将场景添加到构建中
场景在此列表中出现的顺序无关紧要,除了第一个场景(列为场景0 的场景)。列表中的第一个场景应该是游戏加载时要加载的场景。因此,我们将Chapter9-Examples-StartScreen放在位置0是有意义的。
Figure 9.38: Adding the scenes to the build
The order in which scenes appear in this list does not matter, except for the first scene (the one listed as scene 0). The first scene in the list should be the scene you want to load when the game loads. Therefore, it makes sense for us to put Chapter9-Examples-StartScreen in position 0.
使用 UnityEngine;
使用 UnityEngine.SceneManagement;
公共类 LevelLoader:MonoBehaviour {
公共字符串sceneToLoad =“”;
公共无效LoadTheLevel(){
场景管理器.加载场景(场景到加载);
}
}上述代码包含一个函数 - LoadTheLevel()。此函数调用SceneManager类中的LoadScene方法。我们将加载sceneToLoad,这是我们将在Inspector中指定的字符串。请注意,UnityEngine.SceneManagement命名空间必须包含在脚本顶部的以下行中:
使用 UnityEngine.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelLoader : MonoBehaviour {
public string sceneToLoad = "";
public void LoadTheLevel() {
SceneManager.LoadScene(sceneToLoad);
}
}The preceding code contains a single function—LoadTheLevel(). This function calls the LoadScene method in the SceneManager class. We will load the sceneToLoad, which is a string we will specify in the Inspector. Note that the UnityEngine.SceneManagement namespace must be included with the following line at the top of the script:
using UnityEngine.SceneManagement;
图 9.39: LevelLoader.cs 脚本组件
Figure 9.39: The LevelLoader.cs script component
图 9.40:连接的 OnClick 事件
Figure 9.40: The OnClick event hooked up
就是这样!单击播放按钮或在突出显示时按Enter键时,您的播放按钮现在应该导航到Chapter9-Examples场景(就像在开头或使用键盘导航一样)。
That’s it! Your Play Button should now navigate to the Chapter9-Examples scene when clicked on or when you press Enter when it is highlighted (as it is at the beginning or with your keyboard navigation).
按钮通常会以动画的形式吸引玩家的注意力。让我们为播放按钮添加动画过渡,这样当按钮处于正常状态时,它会跳动以吸引玩家的注意力。
Often buttons are animated as a way to draw your attention to them. Let’s give the Play Button an Animation Transition so that when it is in its normal state it will pulsate to draw the attention of the player to it.
To add button Animation Transition on the Play Button, complete the following steps:
您现在应该在新的动画文件夹中看到新的动画控制器:
图 9.41:项目中的播放按钮动画器
您还将在播放按钮上看到新的Animator组件:
图 9.42:播放按钮动画组件
You should now see the new Animator Controller in the new Animations folder:
Figure 9.41: The Play Button animator in the Project
You will also see the new Animator component on the Play Button:
Figure 9.42: The Play Button Animator component
图 9.43:播放按钮上的各种动画
我们要编辑正常过渡状态的动画,因此请确保选择它(默认选择的动画是正常)。
Figure 9.43: The various animations on the Play Button
We want to edit the animation for the Normal transition state, so make sure it is selected (Normal is the animation selected by default).
图 9.44:向播放按钮添加 Scale 属性
图 9.45:播放按钮刻度时间线
Figure 9.44: Adding the Scale property to the Play Button
The Scale property should now be showing up in the Animation timeline:
Figure 9.45: The Play Button Scale timeline
图 9.46:向时间轴添加额外的关键帧
Figure 9.46: Adding an extra keyframe to the timeline
图 9.47:展开播放按钮的缩放属性
Figure 9.47: Expanding the Scale property of the Play Button
现在,当您玩游戏时,按钮应该会跳动(并且不会再变成红色)。请注意,它不会立即跳动,因为按钮只有在正常状态下才会跳动。由于我们已将其设置为First Selected,因此它在启动时被选中。要取消选择,只需单击场景中按钮以外的任意位置或使用键盘导航离开。一旦按钮不再被选中,它就会开始跳动。
Your button should now pulsate when you play the game (and will not turn red any longer). Note that it does not pulsate immediately, because the button will only pulsate when it is in its normal state. Since we have it set to First Selected, it is selected on start. To remove the selection, simply click anywhere in your scene outside of the button or navigate away using your keyboard. Once the button is no longer selected, it should begin pulsating.
图 9.48:选择播放按钮的选定动画
Figure 9.48: Selecting the Selected animation for the Play Button
图 9.49:将颜色属性添加到播放按钮时间线
Figure 9.49: Adding the Color property to the Play Button timeline
图 9.50:调整播放按钮的颜色属性
Figure 9.50: Adjusting the Color property of the Play Button
现在当你玩游戏时,播放按钮在未被选中时应该脉动,被选中时则变为红色。
Now when you play the game, the Play Button should pulsate when it is not selected and change to red when it is.
这标志着有关按钮的示例的结束,但我们将在以后的章节中继续使用它们。
That marks the end of the examples concerning Buttons, but we will continue to use them in future chapters.
一旦你学会了如何使用事件系统,使用按钮就很容易了。按钮是最常见的交互式 UI 元素,因此掌握它们对于有效的 UI 开发至关重要。不过,设置它们以便在点击时发挥作用只是整个过程的一半。如果你要为 PC、Mac或游戏机进行开发,你还需要确保正确设置按钮导航。
Once you learn how to work with the Event System, working with buttons is an easy extension. Buttons are the most common interactive UI element, so having a good grasp on them is essential to effective UI development. Setting them up so that they function when clicked on is only half the process, though. You want to also ensure that you have your button navigation set up properly if you will be developing for PC, Mac, or console.
我们还没有结束对按钮的学习!我们将在整本书中继续使用它们。一旦我们更彻底地探索了图像组件,我们将介绍更多有趣的按钮实现和转换。
We’re not done with Buttons! We’ll be working with them throughout this book. Once we explore the Image component more thoroughly, we will cover more interesting button implementations and transitions.
在下一章中,我们将介绍 UI Text 组件!
In the next chapter, we’ll cover the UI Text component!
我们已经花了一些时间研究 UI Text 对象,因为它们是最基本的图形 UI 元素之一。我们在第 6 章中简要讨论了它们,因为在没有任何视觉显示的情况下开始布局 UI 非常困难。Text 对象也始终是新按钮的子项,我们在上一章中讨论过。但是,我们还没有探索 Text 对象的属性或如何在代码中使用它们。
We’ve spent some time with UI Text objects already as they are the one of most basic graphical UI elements. We discussed them briefly in Chapter 6 because it was pretty hard to start laying out UI without having anything to display visually. Text objects are also always children of new Buttons, which we discussed in the previous chapter. However, we haven’t explored the properties of Text objects or how to work with them in code.
在本章中,我们将更深入地探索UI Text对象。我们还将讨论Text-TextMeshPro对象以及它们如何让我们更好地控制游戏中的文本。
In this chapter, we will explore UI Text objects more thoroughly. We will also discuss Text-TextMeshPro objects and how they allow for even more control of the text in our game.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
笔记
Note
本节展示的所有示例都可以在本书代码包中提供的 Unity 项目中找到。它们可以在第 10 章场景中找到。
All the examples shown in this section can be found within the Unity project provided in this book’s code bundle. They can be found within the Chapter10 scene.
每个示例图像都有一个标题,注明场景中的示例编号。
Each example image has a caption stating the example number within the scene.
在场景中,每个示例都在其自己的画布上,并且一些画布已被停用。要查看已停用的画布上的示例,只需在 Inspector 中选中画布名称旁边的复选框即可。每个画布还被赋予了自己的事件系统。如果您一次激活多个画布,这将导致错误。
In the scene, each example is on its own Canvas, and some of the Canvases have been deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector. Each Canvas has also been given its own Event System. This will cause errors if you have more than one Canvas activated at a time.
您可以在此处找到本章节的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2010
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2010
您可以创建一个新的 UI Text 对象使用+ | UI |文本:
You can create a new UI Text object using + | UI | Text:
图 10.1:UI 文本游戏对象检查器
Figure 10.1: The UI Text GameObject Inspector
UI Text GameObject 包含Rect Transform和Canvas Renderer组件,以及Text组件。
The UI Text GameObject contains the Rect Transform and Canvas Renderer components, as well as the Text component.
UI 文本组件为所连接的对象提供非交互式文本显示。此组件不允许您创建您可能感兴趣的所有类型的文本,但它允许大多数基本的文本显示。
The UI Text component gives the object it is attached to a non-interactive text display. This component does not allow you to create all types of text you may be interested in, but it does allow most basic text displays.
Text属性改变文本将显示。在此框中输入的任何内容都将显示在文本对象中。
The Text property changes the text that will be displayed. Whatever is typed within this box will be displayed within the Text object.
Text属性下方是一组Character属性。这些属性允许您更改Text 属性字段内各个字符的属性。
Below the Text property is a group of Character properties. These properties allow you to change the properties of the individual characters within the Text property’s field.
Font属性决定了整个文本块使用哪种字体。默认情况下,Font设置为Arial 。要使用任何其他字体,您必须将字体导入Asset文件夹。请参阅使用字体部分以了解如何引入其他字体。
The Font property determines which font is used for the entire block of text. By default, the Font is set to Arial. To use any other font, you must import the font into your Asset folder. Refer to the Working with fonts section to learn how to bring in additional fonts.
字体样式提供了随所提供字体提供的可用字体样式的下拉列表。可能的样式有普通、粗体、斜体和粗体和斜体:
Font Style provides a dropdown list of available font styles that come with the provided font. The possible styles are Normal, Bold, Italic, and Bold And Italic:
图 10.2:UI 文本组件的字体样式选项
Figure 10.2: The UI Text component’s Font Style options
笔记
Note
值得注意的是,并非所有字体都支持所有列出的字体样式。
It’s important to note that not all fonts will support all the listed font styles.
字体大小决定文本的大小,而行距表示每行文本之间的垂直间距。
Font Size determines the size of the text, whereas Line Spacing represents the vertical spacing between each line of text.
如果选择了富文本属性,您可以包括标记标签在文本属性字段中,它们将以富文本样式显示,而不是按键入的方式显示。如果未选择此属性,则文本将按键入的方式显示。有关使用富文本书写的更多信息,请参阅标记格式部分吨。
If the Rich Text property is selected, you can include markup tags within the Text property field and they will appear with Rich Text styling rather than as typed. If this property is not selected, the text will display exactly as typed. Refer to the Markup format section for more information concerning writing with Rich Text.
下一组属性 —段落属性(图 10.1) — 允许您确定文本在 Rect Transform 边界内(或边界外)的显示方式。
The next set of properties – the Paragraph properties (Figure 10.1) – allow you to determine how the text will display within (or outside of) the Rect Transform’s bounds.
Alignment属性根据 Rect Transform 边界确定文本的对齐位置。您可以选择水平和垂直对齐选项。按钮表示相对于 Rect Transform 边界的位置,因此左水平对齐将使文本向上推到 Rect Transform 左边界的边缘:
The Alignment property determines where the text will align based on the Rect Transform bounds. You can choose both horizontal and vertical alignment options. The buttons represent the position relative to the Rect Transform bounds, so the left horizontal alignment will have the text pushed up to the edge of the Rect Transform’s left bound:
图 10.3:UI 文本组件的对齐选项
Figure 10.3: The UI Text component’s Alignment options
按几何对齐属性可将文本对齐,就好像字形或字符被裁剪至不透明区域而不是其覆盖区域一样。此裁剪基于其字符映射。这可以提供更紧密的对齐,但也可能导致重叠。
The Align by Geometry property aligns the text as if the glyphs or characters are cropped down to their opaque area rather than the area they cover. This cropping is based on their character map. This can give a tighter alignment but might also cause things to overlap.
水平溢出属性决定了文本对于矩形变换区域来说太宽时会发生什么。有两个选项:Wrap和Overflow。Wrap将导致文本继续在下一行,而Overflow将导致文本扩展到矩形区域之外:
The Horizontal Overflow property determines what happens to text if it is too wide for the Rect Transform area. There are two options: Wrap and Overflow. Wrap will cause the text to continue on the next line, whereas Overflow will cause the text to expand past the rectangular area:
图 10.4:第 10 章场景中的水平溢出示例
Figure 10.4: Horizontal Overflow Example in the Chapter10 scene
垂直溢出属性决定如果文本对于 Rect Transform 区域来说太长,会发生什么情况。有两个选项:Truncate和Overflow。Truncate将截断矩形区域外的所有文本,而Overflow将导致文本超出矩形区域。在下图中,两个文本框都有相同的文本,但Truncate会删除最后两行文本,因为它们超出了矩形区域,而Overflow 则允许它超出框:
The Vertical Overflow property determines what happens to text if it is too long for the Rect Transform area. There are two options: Truncate and Overflow. Truncate will cut off all text outside of the rectangular area, whereas Overflow will cause the text to expand past the rectangular area. In the following figure, both Text boxes have the same text, but Truncate removes the last two lines of text due to them being outside of the rectangular area, while Overflow allows it to go outside the box:
图 10.5:第 10 章场景中的垂直溢出示例
Figure 10.5: Vertical Overflow Example in the Chapter10 scene
Best Fit属性尝试调整文本大小,使其全部适合矩形区域。选择Best Fit属性时,将有两个新属性可用:Min Size和Max Size。这些属性允许您指定字体大小可以保持的范围。
The Best Fit property attempts to resize the text so that all of it fits within the rectangular area. When you select the Best Fit property, two new properties will become available: Min Size and Max Size. These properties allow you to specify the range the font size can maintain.
请记住,根据您所编写的文本,“水平溢出”属性可能会导致其工作方式与您预期的略有不同。
Keep in mind that depending on the text you have written, the Horizontal Overflow property may cause this to work slightly differently than you’d expect.
图 10.6:第 10 章场景中的最佳拟合示例
Figure 10.6: Best Fit Example in the Chapter10 scene
例如,两个文本图 10.6中的框已选择“最佳适配”,但第一个框的“水平溢出”设置为“包裹” ,而第二个框的“水平溢出”设置为“溢出”韓。
For example, the two Text boxes in Figure 10.6 have Best Fit selected, but the first has Horizontal Overflow set to Wrap, while the second has it set to Overflow.
颜色和材质属性允许您调整文本字体的外观。Color属性将设置文本的基本渲染颜色,这是更改字体颜色的最快方法。默认情况下,此属性设置为非常深(不是完全黑色)的灰色。Material属性允许您分配字体的材质。这让您可以更好地控制字体的外观,还允许您应用特定的着色器。默认情况下,此属性设置为N一。
The Color and Material properties allow you to adjust the appearance of the Text’s font. The Color property will set the base rendering color of the Text and is the quickest way to change the font’s color. By default, this property is set to a very dark (not fully black) gray. The Material property allows you to assign a material to your font. This gives you more control over the look of the font and also allows you to apply specific shaders. By default, this property is set to None.
Raycast Target属性决定对象的 Rect Transform 区域是否将阻挡光线投射。如果选中此属性,则点击不会在其后面的 UI 对象上注册。如果未选中,则可以点击对象后面的项目。如果您希望文本阻挡光线投射,但不阻挡其整个区域,您可以使用各种Raycast Padding属性来调整区域。
The Raycast Target property determines whether the object’s Rect Transform area will block raycasts or not. If this property is selected, clicks will not register on UI objects behind it. If it is not selected, items behind the object can be clicked. If you’d like for the Text to block raycasts, but not over its entire area, you can adjust the area with the various Raycast Padding properties.
最后一个属性Maskable决定了文本是否可以被屏蔽。我们将在第 12 章中讨论这个问题。
The last property, Maskable, determines if the Text can be masked. We will discuss this in Chapter 12.
有一点限制了解 UI Text 可以做什么。如果您发现自己想要对文本进行一些无法通过 UI Text 完成的格式化,那么您可以使用 TextMeshPro GameObject 来完成。例如,如果您想使用带下划线的文本,我建议使用 TextMeshPro GameObject。TextMeshPro 资源允许进行更多的文本控制。此外,与标准 UI Text 相比,它的渲染允许文本在更高的分辨率和点大小下清晰显示。
There is a bit of a limitation to what you can do with UI Text. If you find yourself wanting to accomplish some formatting with your text that you are unable to do with UI Text, you may be able to accomplish it with a TextMeshPro GameObject. For example, if you want to use underlined text, I recommend using a TextMeshPro GameObject. TextMeshPro assets allow for significantly more text control. Additionally, its rendering allows text to appear crisp at more resolutions and point sizes than what’s possible with the standard UI Text.
TextMeshPro 曾经是 Unity Asset Store 中的付费资产,但 Unity 于 2017 年 3 月左右采用了它,现在免费提供。但是,要使用 TextMeshPro 资产,您必须下载必要的资源。要下载 TextMeshPro 资源,请尝试通过转到+ | UI | Text - TextMeshPro将 TextMeshPro - Text 添加到您的场景中;您将看到以下弹出窗口:
TextMeshPro used to be a paid asset in the Unity Asset Store, but it was adopted by Unity around March 2017 and is now available for free. However, to use TextMeshPro assets, you have to download the necessary resources. To download the TextMeshPro resources, attempt to add a TextMeshPro - Text to your scene by going to + | UI | Text - TextMeshPro; you will see the following popup:
图 10.7:TMP 导入器
Figure 10.7: The TMP Importer
选择导入 TMP Essentials以获取所有必要的资产。我还建议选择导入 TMP 示例和附加内容。
Select Import TMP Essentials to get all the necessary assets. I also recommend selecting Import TMP Examples & Extras.
笔记
Note
从 UI 菜单中选择任何 TextMeshPro 游戏对象(文本 - TextMeshPro、按钮 - TextMeshPro、下拉菜单 - TextMeshPro 或输入字段 - TextMeshPro)都会弹出前面屏幕截图中的弹出窗口,并允许您下载必要的资产。
Selecting any of the TextMeshPro GameObjects from the UI menu (Text - TextMeshPro, Button - TextMeshPro, Dropdown - TextMeshPro, or Input Field - TextMeshPro) will bring up the popup from the preceding screenshot and allow you to download the necessary assets.
一旦下载后,您无需再次下载。
Once you’ve downloaded it, you will not have to do so again.
由于 TextMeshPro 的稳健性,很遗憾我无法在本章中介绍您可以使用它做的所有事情。相反,我将对其功能进行广泛的概述。幸运的是,TextMeshPro 资产随附有许多示例和良好的文档,可以在这里找到:
Due to the robustness of TextMeshPro, I sadly can’t cover everything you can do with it within this chapter. Instead, I will provide a broad overview of its functionality. Luckily, the TextMeshPro asset comes with many examples and good documentation, which can be found here:
https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/index.xhtml
https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/index.xhtml
当您创建一个新的TextMeshPro - Text GameObject 时,您将看到一个具有以下组件的 GameObject:
When you create a new TextMeshPro - Text GameObject, you will see a GameObject with the following component:
图 10.8:TextMeshPro - 文本组件
Figure 10.8: The TextMeshPro - Text component
您还可以创建 Text-TextMeshPro 游戏对象通过GameObject | 3D Object | Text-TextMeshPro在 Unity 的 UI 系统之外。这将独立于 UI 渲染文本,并且无需Canvas。
You can also create Text-TextMeshPro GameObjects outside of Unity’s UI system via GameObject | 3D Object | Text-TextMeshPro. This will render the text independent of the UI and without a Canvas.
GameObject 本身将被命名为Text (TMP),并且为了简单起见,我将这样引用它。
The GameObject itself will be named Text (TMP) and, for simplicity’s sake, is how I will reference it.
与所有其他 UI 对象一样,Rect Transform和Canvas Renderer组件也附加到 GameObject。Text (TMP) GameObject的图形显示由TextMeshPro - Text ( UI)组件控制。
As with all other UI objects, a Rect Transform and a Canvas Renderer component are attached to the GameObject as well. The graphic display of the Text (TMP) GameObject is controlled by the TextMeshPro - Text (UI) component.
让我们研究一下TextMeshPro – 文本(UI) c的属性组件。
Let’s investigate the properties of the TextMeshPro – Text (UI) component.
You can enter the text you wish to display within the Text Input section:
图 10.9:TextMeshPro - Text 组件的文本输入设置
Figure 10.9: The TextMeshPro - Text component’s Text Input settings
启用 RTL 编辑器属性允许您创建从右到左显示的文本,这对于某些语言来说是必需的。当您选择它时,文本将以从右到左的顺序出现在第二个区域中:
The Enable RTL Editor property allows you to create text that will display from right to left which is necessary for some languages. When you select it, the text will appear in a second area in its right-to-left order:
图 10.10:TextMeshPro - Text 组件的 RTL Text Input 设置
Figure 10.10: The TextMeshPro - Text component’s RTL Text Input setting
文本样式设置可让您指定文本样式。您将从下拉列表中看到多个预定义选项:
The Text Style setting lets you specify a style for the text. You’ll see multiple pre-defined options from the dropdown:
图 10.11:TextMeshPro - Text 组件的文本样式设置
Figure 10.11: The TextMeshPro - Text component’s Text Style settings
We’ll look at how to use this more thoroughly in the Style sheets section of this chapter.
The Main Settings section allows you to adjust all the properties of the text:
图 10.12:TextMeshPro - Text 组件的主要设置部分
Figure 10.12: The TextMeshPro - Text component’s Main Settings section
字体资源属性代表将使用的字体。默认情况下,字体资产设置为LiberationSans SDF (TMP_Font Asset) 。我想指出的是,字体和字体资产是两个不同的东西。字体用于 UI Text GameObjects,而字体资产用于 TextMeshPro GameObjects。我将在使用字体部分讨论这些差异,以及如何导入新字体和创建新的字体资产。
The Font Asset property represents the font that will be used. By default, Font Asset is set to LiberationSans SDF (TMP_Font Asset). I want to point out that fonts and font assets are two different things. Fonts are used in UI Text GameObjects, whereas font assets are used in TextMeshPro GameObjects. I’ll discuss these differences, as well as how to import new fonts and create new font assets, in the Working with fonts section.
Font Asset属性需要材质来渲染。任何包含字体资产名称的材质都会出现在Material Preset列表中。您可以创建自己的材质,但当您创建新的字体资产时,它会附带一个默认材质。无论在此处选择哪种材质,它都会出现在组件底部的Extra Settings下方。从此区域,您还可以选择材质的着色器:
The Font Asset property needs a material to render. Any material that contains the name of the font asset will appear in the Material Preset list. You can create your own material, but when you create a new font asset, it comes with a default material. Whichever material is selected here will also appear at the bottom of the component, below the Extra Settings. From this area, you can also select the material’s shader:
图 10.13:TextMeshPro - 文本的材质属性
Figure 10.13: The TextMeshPro - Text’s material properties
字体样式属性允许您为文本创建基本格式。您可以从粗体、斜体、下划线、删除线、小写、大写或小型大写中进行选择。您可以选择前四个设置的任意组合;但是,您只能在小写、大写或小型大写之间进行选择。
The Font Style property allows you to create basic formatting for your text. You can select from Bold, Italic, Underline, Strikethrough, Lowercased, Uppercased, or Small Caps. You can choose any combination of the first four settings; however, you can only choose between Lowercase, Uppercase, or Small Caps.
Font Size属性的工作方式与你预期的一样,但你也可以选择Auto Size。Auto Size属性将根据你指定的属性,尝试将文本尽可能地适合 Rect Transform 的边界框:
The Font Size property works as you would expect, but you also have the option to select Auto Size. The Auto Size property will attempt to fit the text within the bounding box of the Rect Transform as best as it can based on the properties you specify:
图 10.14:TextMeshPro - Text 组件的字体大小属性
Figure 10.14: The TextMeshPro - Text component’s Font Size properties
您可以指定最小 ( Min ) 和最大 ( Max ) 字体大小以及WD%和Line属性。WD %属性允许您水平挤压文本以使字符更高,而Line属性允许您指定行高。
You can specify the minimum (Min) and maximum (Max) font size along with the WD% and Line properties. The WD% property allows you to squeeze text horizontally to make the characters taller, whereas the Line property allows you to specify line height.
您可以使用“顶点颜色”属性或“颜色渐变”属性来更改文本的颜色:
You can change the color of the text using either the Vertex Color property or the Color Gradient property:
图 10.15:TextMeshPro - Text 组件的颜色属性
Figure 10.15: The TextMeshPro - Text component’s color properties
本章末尾的示例部分中有一个使用颜色渐变属性的示例。如果您希望颜色设置覆盖任何<color>标记标签,则可以选择“覆盖标签” 。
An example of using the Color Gradient property can be found at the end of this chapter in the Examples section. You can select Override Tags if you want the color settings to override any <color> markup tags.
您可以在间距选项区域设置字符、单词、行和段落之间的间距。
You can set the spacing between Characters, Words, Lines, and Paragraphs in the Spacing Options area.
与标准 UI Text 相比,TextMeshPro-Text 中还提供了更多的对齐选项。
You also have significantly more Alignment options available to you in TextMeshPro-Text than you do with the standard UI Text.
就像 UI Text 一样,你可以启用或禁用Wrapping。但是,你有更多的Overflow选项,如以下屏幕截图所示:
Just as with UI Text, you can enable or disable Wrapping. You have significantly more Overflow options, however, as shown in the following screenshot:
图 10.16:TextMeshPro - Text 组件的溢出选项
Figure 10.16: The TextMeshPro - Text component’s Overflow options
Overflow和Truncate 的工作方式与 UI Text 对象上的类似。在其他选项中,我这次只想提一下Ellipsis选项。它将文本截断到文本框区域,但会添加省略号 (…):
Overflow and Truncate work similarly to those on the UI Text objects. Of the other options, the only one I want to mention at this time is the Ellipsis option. It will truncate the text to the text box area but add an ellipsis (…):
图 10.17:第 10 章场景中的 Text Mesh Pro Overflow 示例
Figure 10.17: Text Mesh Pro Overflow Example in the Chapter10 scene
水平映射和垂直映射属性允许您影响纹理在文本中的显示方式:
The Horizontal Mapping and Vertical Mapping properties allow you to affect the way a texture is displayed across the text:
图 10.18:TextMeshPro - Text 组件的水平映射选项
Figure 10.18: The TextMeshPro - Text component’s Horizontal Mapping options
你所做的大部分工作是为了定制你的字体将在“文本输入”部分和“主要设置”部分中完成,但让我们看看你将根据自己的需要调整的一些更细微的设置。字型。
Most of the work you do to customize your font will be done in the Text Input section and the Main Settings section, but let’s look at some more nuanced settings that you will adjust with your fonts.
必须扩展额外设置菜单可见。它允许您调整字体的一些不太常见的设置:
The Extra Settings menu has to be expanded to be visible. It allows you to adjust some less-common settings of the font:
图 10.19:TextMeshPro - 文本额外设置部分
Figure 10.19: The TextMeshPro - Text Extra Settings section
此菜单中最值得注意的属性是添加Margins的能力和启用Raycast Target的能力。
The most notable properties in this menu are the ability to add Margins and the ability to enable Raycast Target.
最后,您可以指定是否是否要启用字距调整或允许额外填充。选择字距调整将使用字体文件提供的字距调整数据。选择额外填充将在其精灵图集上的精灵字形周围添加一些填充。
Lastly, you can specify whether or not you want to Enable Kerning or allow Extra Padding. Selecting Kerning will use the kerning data provided by the font file. Selecting Extra Padding will add a little padding around the glyphs of the sprite on its sprite atlas.
笔记
Note
您可以了解我在这里忽略或跳过的任何属性:https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/TMPObjectUIText.xhtml。
You can learn about any of the properties I glossed over or skipped here: https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/TMPObjectUIText.xhtml.
除了能够要通过其组件调整场景中每个 TextMeshPro 对象的单独设置,您还可以通过项目设置(编辑>项目设置)调整 TextMeshPro 项目范围的设置:
In addition to being able to adjust the individual settings of each TextMeshPro object you have within your scene via their components, you can also adjust TextMeshPro project-wide settings via the Project Settings (Edit > Project Settings):
图 10.20:TextMeshPro – 项目设置
Figure 10.20: The TextMeshPro – Project Settings
这些可以让你设置各种默认为新创建了 TextMeshPro 对象。您可以在https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/Settings.xhtml了解有关每个属性的更多信息。
These let you set the various defaults for newly created TextMeshPro objects. You can learn more about each property at https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/Settings.xhtml.
极有可能您不会想使用默认的Arial(UI Text)或Liberation Sans(TMP Text)字体,而想将自定义字体引入您的项目。让我们探索如何找到这些文本资源并在您的项目中使用它们。
It’s extremely likely that you won’t want to use the default Arial (UI Text) or Liberation Sans (TMP Text) fonts and will want to bring a custom font into your project. Let’s explore how you can both find these text resources and use them in your project.
笔记
Note
您不必在计算机上安装字体(在 Unity 之外的程序中使用)即可在 Unity 中使用该字体。
You do not have to install a font onto your computer (to use within programs outside of Unity) to use the font within Unity.
字体文件格式Unity 接受的格式是.tff (TrueType) 和.otf (OpenType)。您可以在多个地方获取这些文件。我的最喜欢的地方查找字体如下:
The font file formats accepted by Unity are .tff (TrueType) and .otf (OpenType). You can get these files in multiple places. My favorite places to find fonts are as follows:
Google Fonts上的所有字体都是开源的,个人或商业用途均可免费使用(至少在撰写本书时),但Font Squirrel和DaFont上的字体有不同的许可选项。在使用任何字体之前,请确保它都有符合您需求的许可协议。
All the fonts on Google Fonts are open source and are free for personal or commercial use (at least at the time of writing this book), but the fonts on Font Squirrel and DaFont have varying licensing options. Ensure that any font you get has a licensing agreement that meets your needs before you use it.
下载完所选字体后,只需将字体拖到项目的Assets文件夹中即可。我强烈建议您在Assets文件夹中创建一个名为Fonts的文件夹,将所有字体文件都放在其中。
Once you’ve downloaded the font of your choice, simply drag the font into your project’s Assets folder. I highly recommend that you create a folder called Fonts within your Assets folder in which you place all of your font files.
然后,您可以选择在Inspector中调整字体的导入属性。以下屏幕截图显示了从 Google Fonts 下载的BungeeShade-Regular字体的导入设置:
Then, you can adjust the font’s import properties in the Inspector if you so choose. The following screenshot shows the import settings of the BungeeShade-Regular font, which has been downloaded from Google Fonts:
图 10.21:Bungee Shade-Regular 字体检查器
Figure 10.21: The Inspector of the Bungee Shade-Regular font
Here, you can adjust quite a few of the settings concerning how the font will be handled by the engine.
字体大小设置决定字体在 Unity 创建的纹理图集中显示的大小。增加或减少“字体大小”设置将改变纹理图集中各种字形的大小。如果您的字体在游戏中显得模糊,调整“字体大小”设置可能会改善 它的外观。
The Font Size setting determines how large the font will appear on its Unity-created texture atlas. Increasing or decreasing the Font Size setting will change the size of the various glyphs on the texture atlas. If your font appears fuzzy in your game, adjusting the Font Size setting may improve its appearance.
渲染模式设置告诉 Unity 如何字形将被平滑处理。可能的选项包括Smooth、Hinted Smooth、Raster和OS Default。
The Rendering Mode settings tell Unity how the glyphs will be smoothed. The possible options are Smooth, Hinted Smooth, Raster, and OS Default.
平滑渲染模式是最快的渲染模式。它使用抗锯齿渲染,这意味着它将平滑锯齿状、像素化的边缘。提示平滑渲染模式也会平滑边缘,但它将使用字体数据文件中包含的“提示”来确定如何填充这些锯齿状边缘。这种渲染模式比平滑渲染模式慢,但看起来可能比平滑更清晰,更容易阅读。提示光栅渲染模式不提供抗锯齿,而是提供锯齿或锯齿状边缘。这是最清晰、最快的渲染模式。操作系统默认将默认为 Windows 或 Mac OS 上操作系统的首选项设置。这将从平滑中选择 或暗示平滑。
The Smooth rendering mode is the fastest rendering mode. It uses anti-aliased rendering, which means it will smooth out jagged, pixelated edges. The Hinted Smooth rendering mode will also smooth out edges, but it will use the “hints” contained within the font’s data files to determine how to fill in those jagged edges. This is a slower rendering mode than Smooth but will likely look crisper and be easier to read than Smooth. The Hinted Raster rendering mode does not provide anti-aliasing and instead provides aliased or jagged edges. This is the crispest and the quickest of the rendering modes. OS Default will default to whatever the operating system’s preferences are set to on Windows or Mac OS. This will select from Smooth or Hinted Smooth.
Character属性决定字体的哪种字符集将被导入到字体纹理图集中。有六个选项:动态、Unicode、ASCII 默认集、ASCII 大写、ASCII 小写和自定义集:
The Character property determines which character set of the font will be imported into the font texture atlas. There are six options: Dynamic, Unicode, ASCII default set, ASCII upper case, ASCII lower case, and Custom set:
将Character属性设置为Dynamic(默认)将仅包含所需的角色。这会减少字体所需的纹理大小,从而减少游戏的下载大小。
Setting the Character property to Dynamic (which is the default) will only include the characters that are needed. This reduces the texture size needed for the font and, in turn, the download size of the game.
Unicode用于包含 ASCII 字符集不支持的字符的语言。因此,例如,如果您想显示日语文本,则需要使用Unicode。
Unicode is used for languages that have characters that are not supported in an ASCII character set. So, for example, if you want to display text in Japanese, you will want to use Unicode.
如果您想在脚本中包含Unicode字符,则需要使用 UTF-16 编码保存它们。这样您就可以在代码中直接将Unicode字符作为字符串输入,以便它们可以在屏幕上的文本对象中显示。
If you’d like to include Unicode characters in your scripts, you need to save them with UTF-16 encoding. This will allow you to type Unicode characters directly in your code as strings so that they can display in your Text objects on screen.
Google Fonts提供的Noto 字体支持多种语言,如果你想创建一款翻译游戏,它会非常有用翻译成多种语言。Noto字体可在https://www.google.com/get/noto/找到。
The Noto fonts provided by Google Fonts provide support for many languages and can be very helpful if you want to create a game that is translated into multiple languages. The Noto fonts can be found at https://www.google.com/get/noto/.
美国信息交换标准代码( ASCII ) 是一组来自英语字符的字符集。ASCII 字符集的三种变体允许您在全集、仅大写或仅小写字符之间进行选择。您可以在http://ascii.cl/找到ASCII字符列表。
American Standard Code for Information Interchange (ASCII) is a set of characters from the English-language character set. The three variations of ASCII character sets allow you to choose between the full set, only uppercase, or only lowercase characters. You can find a list of the ASCII characters at http://ascii.cl/.
自定义字符集将允许您为自己的自定义字体导入自己的纹理图集。我发现当开发人员想要美化文本时,这种做法最常用,因为文本数量非常有限字符集,例如仅限数字。
A Custom set character will allow you to import your own texture atlas for your own custom font. I find this most commonly used when developers want beautified text with an extremely limited character set, such as numbers only.
字体的上升是距离字体基线和最高字形点之间的距离。没有关于如何确定这个假定的最高字形点的标准,因此 Unity 中提供了不同的模式可供选择,每种模式确定不同的最高字形点。上升计算模式属性决定了如何计算上升。对于如何选择此计算,有三个选项:旧版本 2 模式(字形边界框)、Face 上升度量和Face 边界框度量。所选方法可能会影响字体的垂直对齐。
The ascent of a font is the distance between the font’s baseline and the highest glyph point. There is no standard for how this supposed highest glyph point is determined, so different modes are available in Unity to choose from, each determining a different highest glyph point. The Ascent Calculation Mode property determines how the ascent will be calculated. There are three options for how this calculation will be chosen: Legacy version 2 mode (glyph bounding boxes), Face ascender metric, and Face bounding box metric. The method chosen may affect the vertical alignment of the font.
旧版 2 模式(字形边界框)使用字体字符集中列出的任何字形所达到的最高点作为高度来测量上升。这仅使用字符集中列出的字形,并且并非所有字形都可能包含在该集合中。面上升度量使用定义为测量上升的面上升值,而面边界框度量使用面边界框来测量上升。
Legacy version 2 mode (glyph bounding boxes) measures the ascent using the highest point reached by any one of the font’s glyphs listed within its character set as the height. This only uses those listed in the character set, and not all glyphs may be included within that set. Face ascender metric uses the face ascender value that is defined to measure the ascent, whereas Face bounding box metric uses the face bounding box to measure the ascent.
排版比很多人想象的要复杂得多,这本书无法完全涵盖它——更不用说我不是排版专家。如果你想了解更多关于字形指标,可以在https://www.freetype.org/freetype2/d找到很好的介绍ocs/glyphs/glyphs-3.xhtml。
Typography is a lot more complicated than many people realize, and it’s too complicated to fully cover in this book – not to mention I am no typography expert. If you’d like to learn more about glyph metrics, a good introduction can be found at https://www.freetype.org/freetype2/docs/glyphs/glyphs-3.xhtml.
当你导入字体时使用动态字符集,提供了两个新设置:包括字体数据和字体名称。
When you import your font with a dynamic character set, two new settings are made available: Include Font Data and Font Names.
包含字体数据会随游戏一起构建字体文件。如果未选择此选项,游戏将假定玩家已在其机器上安装该字体。如果您使用的是从网上下载的字体,那么可以肯定的是,最终用户不会安装该字体,因此您应该保留包含字体数据的选项。
Include Font Data builds the font file with the game. If this is not selected, the game will assume that the player has the font installed on their machine. If you are using a font you have downloaded from the web, it is a pretty safe bet that the end user will not have the font installed and you should leave Include Font Data selected.
字体名称是字体名称列表,如果 Unity 找不到字体,它将使用这个字体名称。如果字体不包含请求的字形或 Incl . Font Data属性被取消选择,并且用户的机器上没有安装该字体,则需要使用此字体名称。如果 Unity 找不到字体,它将在游戏的项目文件夹或用户的机器中搜索与字体名称中列出的名称之一匹配的字体。将字体输入到字体名称后,相应的字体将在项目中对其他字体的引用部分中列出:
Font Names is the list of names of fonts that Unity will fall back on if it cannot find the font. It will need to fall back on this font name if the font doesn’t include the requested glyph or the Incl. Font Data property was deselected and the user does not have the font installed on their machine. If Unity cannot find the font, it will search the game’s project folder or the user’s machine for a font matching one of the names listed in Font Names. Once the fonts have been typed into Font Names, the appropriate fonts will be listed in the References to other fonts in project section:
图 10.22:Roboto-Regular 字体的检查器
Figure 10.22: The Inspector of the Roboto-Regular font
如果 Unity 找不到列出的字体之一,它将使用Unity 内硬编码的预定义后备字体列表中提供的字体。
If Unity cannot find one of the fonts listed, it will use a font provided in a predefined list of fallback fonts hard-coded within Unity.
有些平台没有系统中没有内置字体或无法访问内置字体。这些平台包括 WebGL 和一些控制台系统。在构建到这些平台时,Unity 将始终包含字体,无论设置选择如何sen包含字体数据。
Some platforms don’t have built-in fonts in their system or can’t access built-in fonts. These platforms include WebGL and some console systems. When building to these platforms, Unity will always include fonts, regardless of the setting chosen for Include Font Data.
如果您引入了具有多个样式(即粗体、斜体或粗体和斜体),您必须引入所有字体样式才能正确显示。但是,将字体引入项目可能不足以使字体识别在选择粗体、斜体和粗体和斜体字体样式时应使用哪些字体。例如,以下屏幕截图显示了文本顶行具有粗体和斜体字体样式的Roboto-Regular Google 字体和文本底行具有普通字体样式的Roboto-BoldItalic Google 字体:
If you bring in a font that has multiple styles (that is, bold, italic, or bold and italic), you must bring in all the font styles for them to appear properly. Bringing the fonts into your project may not be sufficient for the font to recognize which fonts should be used when the Bold, Italic, and Bold and Italic font styles are selected, however. For example, the following screenshot shows the Roboto-Regular Google Font with the Bold and Italic Font Style on the top line of text and the Roboto-BoldItalic Google Font with the Normal Font Style on the bottom line:
图 10.23:Roboto 字体的样式
Figure 10.23: Styles of the Roboto font
如果Font Style属性正常工作,则两行应该匹配。但是,如您所见,它们不匹配。要使字体正确显示,请选择常规字体,重新输入Font Name属性以使所有适当的字体出现在字体列表中(如图 10.22所示),然后单击Apply 。执行此操作后,两个字体应该看起来相同:
If the Font Style property were working correctly, the two lines should match. However, as you can see, they do not. To make the fonts appear correctly, select the regular font, retype the Font Name property to make all the appropriate ones appear in the font list (as shown in Figure 10.22), and hit Apply. After doing so, the two fonts should appear the same:
图 10.24:正确应用 Roboto 字体样式
Figure 10.24: Styles of the Roboto font applied correctly
现在我们已经了解了如何导入字体,让我们看看如何创建自定义字体。
Now that we’ve reviewed how to import fonts, let’s look at how to create custom fonts.
您可以创建自定义字体通过从项目窗口中选择创建|自定义字体。要使用自定义字体,您需要字体材质和字体纹理。本章的示例部分介绍了如何执行此操作。创建自定义字体后,您将获得以下要设置的属性:
You can create a custom font by selecting Create | Custom Font from the project window. To use a custom font, you will need a font material and font texture. How to do this is covered in the Examples section of this chapter. Once you create your custom font, you will be given the following properties to set:
图 10.25:自定义字体的检查器
Figure 10.25: A custom font’s Inspector
行距属性指定每行文本之间的距离。
The Line Spacing property specifies the distance between each line of text.
Ascii Start Offset属性定义字体字符中的第一个 ASCII 字符集。例如,如果您创建的字体仅包含数字,则您的字符集将以数字0开头,即 ASCII 索引48。因此,您可以将ASCII 起始偏移量设置为48,表示此字体的字符集中的第一个字符是字符 0。您可以在http://ascii.cl/上确定各个字符的 ASCII 索引号。
The Ascii Start Offset property defines the first ASCII character in the font’s character set. For example, if you created a font that only included numbers, your character set would start with the number 0, which is ASCII index 48. Therefore, you would set the ASCII Start Offset to 48, indicating the first character in this font’s character set is the character 0. You can determine the ASCII index number for individual characters at http://ascii.cl/.
Tracking属性表示整行文本的字符间距。它允许所有字符之间的间距均匀。
The Tracking property represents the spacing between characters for a full line of text. It allows the spacing between all characters to be uniform.
Kerning属性已被Tracking属性取代。Tracking和Kerning都是与字符间距相关的属性,但它们是不同的。有关更多信息信息,请查看http://www.practicalecommerce.com/Typography-101-The-Basics。
The Kerning property has been replaced with the Tracking property. Tracking and Kerning are both properties related to spacing between characters, but they are different. For more information, check out http://www.practicalecommerce.com/Typography-101-The-Basics.
字符间距是字符之间的空间量,而字符填充是间距之前各个字符周围的填充量。
Character Spacing is the amount of space between characters, whereas Character Padding is the amount of padding surrounding individual characters before the spacing.
Character Rects属性决定了字体中总共有多少个字符。将数字从 0 更改为任何正数将提供可扩展的元素列表:
The Character Rects property determines how many total characters are in your font. Changing the number from 0 to any positive number will provide a list of Elements that can be expanded:
图 10.26:自定义字体的字符矩形
Figure 10.26: The Character Rects of a custom font
每个元素代表字符集上的一个字符。索引是指定字符的 ASCII 索引。
Each element represents a character on your character set. The Index is the ASCII index of the specified character.
UV宽度( W ) 和高度 ( H ) 值字体宽度的百分比以及字符所占的高度。例如,如果您的字体文件包含五列字符和两行字符,则W将是五分之一或.2,而H将是二分之一或.5。这应该在所有字符中保持一致。X和Y值由W和H值乘以字符所在的列或行来确定。
The UV width (W) and height (H) values of your font represent the percentage of the width and height your characters occupy. For example, if you had a font file with five columns of characters and two rows of characters, the W would be one-fifth or .2 and the H would be one-half or .5. This should be consistent throughout all of your characters. The X and Y values are determined by multiplying the W and H values by the column or row that the character is located in.
Vert属性表示字符的宽度和高度(以像素为单位)。H值始终为负数。因此,如果字符的像素尺寸为 50 x 50,则W和H值分别为50和-50 。说实话,我不太清楚 height 属性为什么始终为负数,而且似乎找不到答案。我怀疑这与使用纹理坐标有关。Vert的X和Y值表示位置的变化,这些数字可以是负数也可以是正数。
The Vert properties represent the width and height of the character in pixels. The H value is always negative. So, if a character’s pixel dimension is 50 by 50, the W and H values would be 50 and -50, respectively. To be perfectly honest, I am not exactly sure why the height property is always negative and I can’t seem to find the answer. I suspect that it has something to do with the fact that this uses texture coordinates. The X and Y values of Vert represent a shift in position, where these numbers can be negative or positive.
前进设置表示特定字符和下一个字符之间的像素距离。
The Advance setting represents the pixel distance between the specific character and the next character.
翻转设置表示字形是否翻转或应如何显示。
The Flipped setting indicates if the glyph is flipped of how it should be displayed.
请参阅示例部分,获取计算自定义字体的UV、Vert和Advance值的示例。
Please refer to the Examples section for an example of calculating the UV, Vert, and Advance values of a custom font.
在撰写本文时,Unity 的文档中并未完全定义自定义字体的所有属性,其中一些属性有点模糊。例如,Convert Case曾经是一个下拉菜单,我不清楚它现在如何使用,因为它只接受数字输入。也许,在未来,这些属性将得到更好的定义和手册将会更新以反映所做的新更改,网址为https://docs.unity3d.com/Manual/class-Font.xhtml。
At the time of writing, not all the properties for custom fonts are fully defined within Unity’s documentation and a few of these properties are a bit ambiguous. For example, Convert Case used to be a dropdown menu, and it is unclear to me how it is now used since it only accepts a number input. Perhaps, in the future, these properties will be better defined and the manual will be updated to reflect the new changes made, at https://docs.unity3d.com/Manual/class-Font.xhtml.
回想一下,TextMeshPro 对象需要使用字体资源,而不是字体。因此,如果您为项目下载了一些字体,您将不会在 TextMeshPro 对象中看到它们作为可能的字体选项。如果您导入了 TextMeshPro 示例,除了 Liberation Sans 之外,您可能没有其他可用的选项。要在 TextMeshPro GameObject 中使用不同的字体,您不能简单地将新字体拖到Font Asset插槽中;您必须通过Font Asset Creator创建 Font Asset 。
Recall that TextMeshPro objects need to use font assets, not fonts. So, if you’ve downloaded some fonts for your project, you won’t see them as possible font options with a TextMeshPro object. If you’ve imported the TextMeshPro examples, you may have few options other than Liberation Sans available to you. To use a different font in a TextMeshPro GameObject, you cannot simply drag a new font into the Font Asset slot; you must create a Font Asset via the Font Asset Creator.
要访问Font Asset Creator,请选择Window | TextMeshPro - Font Asset Creator。这将允许您将字体文件转换为 TextMeshPro 可以使用的字体资产:
To access the Font Asset Creator, select Window | TextMeshPro - Font Asset Creator. This will allow you to convert a font file into a font asset that can be used by TextMeshPro:
图 10.27:字体资产创建器窗口
Figure 10.27: The Font Asset Creator window
有很多设置可以通过Font Asset Creator进行控制。您可以在 https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/FontAssetsCreator.xhtml?q=font%20asset%20creator 找到这些设置的详细说明。我将在示例部分介绍创建字体资产的过程本章。
There are quite a few settings that can be controlled via the Font Asset Creator. You can find a breakdown of these settings at https://docs.unity3d.com/Packages/com.unity.textmeshpro@4.0/manual/FontAssetsCreator.xhtml?q=font%20asset%20creator. I will cover the process of creating a font asset in the Examples section of this chapter.
类似 HTML 的标记语言可以包含在Text或TextMeshPro - Text (UI)组件的文本字段中以格式化文本。此标记格式类似于 HTML,因为它使用尖括号标记来包围要格式化的文本。标记使用<tag>要格式化的文本</tag>格式,您可以在其中用适当的标记替换标记。这些标记可以嵌套,就像在 HTML 中一样。
HTML-like markup language can be included within the text field of the Text or TextMeshPro - Text (UI) components to format the text. This markup format is HTML-like in that it uses angle bracket tags around the text that is to be formatted. The tags use the <tag>the text you wish to format</tag> format, where you replace tag with the appropriate tag. These tags can be nested, just as they can in HTML.
要在Text对象上使用标记格式,您必须首先在Text组件中选择Rich Text属性。默认情况下,它在TextMeshPro对象中起作用。
To use markup format on a Text object, you must first select the Rich Text property within the Text component. By default, it works within a TextMeshPro object.
此格式允许您更改字体样式、字体颜色和字体大小。下表列出了所需的标签执行指定的格式化:
This formatting allows you to change the font style, font color, and font size. The following chart lists the tags necessary to perform the specified formatting:
|
格式 Format |
标签 Tag |
|
大胆的 Bold |
b b |
|
斜体 Italic |
我 i |
|
颜色 Color |
颜色 color |
|
尺寸 Size |
尺寸 size |
Table 10.1: Formats and their tags
现在,让我们看看如何使用标记改变字体样式。
Now, let’s look at how to change the style of a font with markup.
You can change the font style of text using the bold and italic tags.
要为文本添加粗体字体样式,请在要加粗的文本周围添加<b></b>标签。要为文本添加斜体字体样式,请在要斜体的文本周围添加<i></i>标签。
To add a bold font style to text, add the <b></b> tags around the text you wish to bold. To add an italic font style to text, add the <i></i> tags around the text you wish to italicize.
Table 10.2: Examples of formatting tags
现在,让我们看看如何使用标记改变字体的颜色。
Now, let’s look at how to change the color of a font with markup.
你可以改变颜色你的字体与十六进制值表示数字或使用颜色名称。要更改文本的颜色,请在要着色的文本周围添加<color=value></color>,其中可以放置十六进制值(在 # 后面)或出现单词值的颜色名称。
You can change the color of your font with either the hex value representation of a number or using the color name. To change the color of the text, add <color=value></color> around the text you wish to color, where you place either the hex value (following a #) or the color name where the word value appears.
只有有限的一组颜色具有可以替换十六进制值的名称。可识别的颜色名称包括黑色、蓝色、棕色、青色、深蓝色、绿色、灰色、浅蓝色、黄绿色、洋红色、栗色、深蓝色、橄榄色、橙色、紫色、红色、银色、青绿色、白色和黄色。您还可以使用浅绿色代替青色,使用紫红色代替洋红色。
Only a limited set of colors have names that can replace the hex values. The color names that are recognized are black, blue, brown, cyan, darkblue, green, grey, lightblue, lime, magenta, maroon, navy, olive, orange, purple, red, silver, teal, white, and yellow. You can also use aqua in place of cyan and fuchsia in place of magenta.
使用颜色标签时,任何未被颜色标签包围的文本都将根据所选的颜色 属性进行着色。
When using the color tag, any text not surrounded by the color tag will be colored based on the Color property selected.
下表显示了如何使用颜色标签:
The following table shows how to use the color tag:
Table 10.3: Color formatting tag examples
现在,让我们看看如何使用标记改变字体的大小。
Now, let’s look at how to change the size of a font with markup.
更改字体尺寸,在周围添加<size=#></size>标签您希望调整大小的文本。标签内未包含的任何文本将根据Font Size属性设置调整大小。以下示例显示如何使用size标签:
To change the font size, add the <size=#></size> tags around the text you wish to resize. Any text not within the tag will be sized based on the Font Size property setting. The following example shows how to use the size tag:
Table 10.4: Examples of multiple font size tags
到目前为止,我们已经了解了使用标记格式化字体的方法。现在,让我们看看如何使用样式表格式化字体。
So far, we’ve looked at the ways we can format fonts with markup. Now, let’s look at how to format a font using a style sheet.
除了标记之外除了上一节中描述的标签之外,TextMeshPro 对象还可以使用样式表标签。您可能还记得图10.9中,TextMeshPro - Text (UI)组件具有一个标记为“文本样式”的属性,其下拉列表中有十个选项。从那里,您可以选择下图所示的任何一种样式:
In addition to the markup tags described in the preceding section, TextMeshPro objects can also use style sheet tags. As you may recall from Figure 10.9, the TextMeshPro - Text (UI) component has a property labeled Text Style with ten options in its dropdown. From there, you can select any one of the styles shown in the following figure:
图 10.28:默认样式表中的各种默认样式
Figure 10.28: The various default styles from the default style sheet
所有这些样式都是预定义的通过为项目设置中的TextMeshPro 设置分配的默认样式表选项(正如我们在TextMeshPro 项目设置部分中所讨论的)。
All of these styles are pre-defined by the Default Style Sheet option that’s assigned for the TextMeshPro Settings within the Project Settings (as we discussed in the TextMeshPro Project Settings section).
图 10.29:项目设置中的默认样式表设置
Figure 10.29: The Default Style Sheet setting from the Project Settings
单击项目设置中的默认样式表(TMP_Style Sheet)对象将选择资产文件夹中的默认样式表:
Clicking on the Default Style Sheet (TMP_Style Sheet) object within the Project Settings will select the Default Style Sheet within the Assets folder:
图 10.30:资源中的默认样式表资产
Figure 10.30: The Default Style Sheet asset within Resources
然后,您可以查看默认样式表资产的检查器,并查看每个默认样式sheet标签定义如下:
You can then view the Default Style Sheet asset’s Inspector and see how each of the default style sheet tags are defined:
图 10.31:默认样式表属性
Figure 10.31: The Default Style Sheet properties
您可以通过编辑此资产随意更改任何这些标签的名称和属性。您可以添加和删除标签。
You are free to change the names and properties of any of these tags by editing this asset. You can add and remove tags.
您还可以创建一个全新的样式表并将其设为默认样式表。要创建新的样式表,请在Assets文件夹中单击鼠标右键,然后选择“创建” | “TextMeshPro” | “样式表”。
You can also create a whole new style sheet and make it the default style sheet. To create a new style sheet, right-click within the Assets folder and select Create | TextMeshPro | Style Sheet.
您可以通过下拉菜单应用这些样式,正如我在本节开头所演示的那样,或者您可以使用<style>标签通过标记格式将它们分配给文本的各个部分。例如,如果您想显示图 10.32中所示的文本,您可以这样做在文本字段中输入以下<style="Title">如何</style>使用<style="Link">内联</style>样式!:
You can apply these styles via the dropdown, as I demonstrated at the beginning of this section, or you can assign them to portions of the text via markup format using the <style> tag. For example, if you wanted to display the text shown in Figure 10.32, you could do so by typing Here's <style="Title">how</style> to use styles <style="Link">in-line</style>! into the Text field:
图 10.32:以标记方式内联使用的样式
Figure 10.32: Styles used in-line in a markup fashion
现在我们已经回顾了如何格式化字体和标记,让我们继续探索如何使用各种文本组件属性以及翻译。
Now that we’ve reviewed the how to format fonts and with markup, let’s move on to explore how you can use the various Text component properties, with translations.
如果你正在创建如果您的游戏中包含文字,您可能需要翻译该文字。为了确保您的游戏易于翻译,您可以采取一些关键措施,让游戏更容易地转换到不同的语言。
Odds are, if you are creating a game with text in it, you might want to translate that text. To make sure your games are easily translatable, there are a few key things that you can do to make the transition to different languages easier.
您要确保,如果翻译后的文本变长或变短,它仍能适合必要的区域。您可以使用文本组件的“按几何对齐”和“最佳拟合”属性来实现这一点。这将使文本适合所需的空间。您还可以使用内容大小调整器(我们在第 7 章中讨论过)来确保文本周围的任何面板都会缩小或扩大,以完美适合文本。如果您不介意不同语言的字体大小不同,则可以使用第一个选项。如果您希望字体大小在不同语言中保持一致,可以使用第二个选项。请记住,某些语言可以在非常小的空间内呈现单个短语,而其他语言则需要在非常大的空间内呈现它。
You want to make sure that the text you plan to translate will still fit within the necessary area if it gets longer or shorter when translated. You can accomplish this by using the Align By Geometry and Best Fit properties of the Text component. This will make the text fit within the required space. You could also use the Content Size Fitter (which we discussed in Chapter 7) to make sure any Panels around the text will shrink or expand to fit perfectly around the text. You can use the first option if you don’t mind the font size varying across languages. You can use the second if you want the font size to remain consistent across languages. Keep in mind that some languages can render a single phrase in a very small amount of space, while others will render it in a very large amount of space.
使用可以支持您要翻译的语言的字体。如果可能的话,最好使用一种适用于所有语言的字体,因为它将在所有翻译中保持一致的风格。您花了很多时间挑选完美的字体!您不希望在翻译游戏时将所有这些都抛到九霄云外,并且字体无法呈现该语言的所有字形!它不是一种华丽或特别时尚的字体,但可以翻译成多种语言的是 Noto Sans 字体系列。如果您知道您将要翻译成多种语言,您可能需要考虑它:https ://fonts.google.com/noto/fonts 。
Use fonts that can support the languages you will be translating to. If possible, a single font that works across all languages will be preferred as it will maintain a consistent style across all translations. You spent so much time picking the perfect font! You don’t want all that to be thrown out the window when you translate your game and the font won’t render all the glyphs of the language! It’s not a glamorous or particularly stylish font, but one that will translate into many languages is the Noto Sans font family. If you know you’ll be translating into a lot of languages, you might want to consider it: https://fonts.google.com/noto/fonts.
如果您计划翻译成从右到左呈现的语言,例如阿拉伯语,则需要使用 TextMeshPro 对象而不是 Text 对象,因为它允许您从 RTL 呈现文本。
If you plan to translate into a language that renders right-to-left, such as Arabic, you will need to use a TextMeshPro object, rather than a Text object, as it will let you render text from RTL.
笔记
Note
在撰写本文时,TextMeshPro 虽然能够从右到左渲染文本,但并不完全支持从右到左的语言。如果您想在游戏的 UI 中显示阿拉伯语、波斯语和/或希伯来语,我推荐以下软件包:https ://github.com/pnarimani/RTLTMPro 。
At the time of writing, TextMeshPro, while able to render text from right-to-left, does not fully support right-to-left languages. If you’d like to display Arabic, Farsi, and/or Hebrew in your game’s UI, I recommend the following package: https://github.com/pnarimani/RTLTMPro.
您可能还会喜欢以下有关渲染从右到左文本的教程: https: //allcorrectgames.com/insights/unity-from-right-to-left/。
You may also appreciate the following tutorial on rendering right-to-left text: https://allcorrectgames.com/insights/unity-from-right-to-left/.
以下内容也非常有用的资源,因为它详细介绍了如何为您的项目构建本地化解决方案,并讨论了从右到左的文本翻译:https ://phrase.com/blog/posts/localizing-unity-games-official-localization-package/ 。
The following is also an extremely helpful resource as it breaks down how to architect a localization solution for your project and also discusses right-to-left text translation: https://phrase.com/blog/posts/localizing-unity-games-official-localization-package/.
在示例部分,我提供了一个专注于翻译的 UI 布局和字体方面的小示例。
In the Examples section, I provide a small example that focuses on the UI layout and font aspects of translation.
在本章中,我们将进一步扩展我们已经构建的场景,并添加一个发生在开始屏幕和主游戏屏幕之间的新场景。
In this chapter, we’ll expand on the scene we’ve been building further and also add a new scene that occurs between our start screen and our main game screen.
首先,我们将创建一个新的场景,它就像我们的开始屏幕和游戏场景之间的过场动画。它将包括我们的猫自我介绍。文本将像正在输入一样动画,用户可以选择通过按下按钮来加快速度。文本完全显示后,按下同一个按钮将显示下一个文本块或转到游戏场景。文本窗口将显示如下:
First, we will create a new scene that acts like a cut scene between our start screen and our gameplay scene. It will include our cat introducing itself. The text will animate as if it is being typed, and the user will have the option to speed it up by pressing a button. Once the text is fully displayed, pressing that same button will either show the next block of text or go to the gameplay scene. The text windows will appear as follows:
图 10.33:动画文本框的最终结果
Figure 10.33: The end result of our animated text box
Let’s start by creating a prefab to save us some development time.
在我们能够开始制作动画文本,我们需要构建场景。在迄今为止创建的两个场景中,我们都使用了Background Canvas来显示背景图像,我们将在新场景中再次使用它。
Before we can start making animated text, we need to build out our scene. In both the scenes we have created so far, we used Background Canvas to display the background image, and we will use it again in a new scene.
由于我们将多次使用此背景画布,因此我们应该创建一个背景画布预制件。正如我们了解到的在上一章中,预制件是可重复使用的游戏对象。在场景中使用预制件会在场景内创建预制件的实例。如果您对已保存的预制件进行更改,则更改将反映在所有场景中所有未中断的预制件实例中。
Since we will use this Background Canvas multiple times, we should create a Background Canvas prefab. As we learned in a previous chapter, a prefab is a reusable GameObject. Using a prefab in a scene creates an instance of the prefab within the scene. If you make a change to the saved prefab, the change will be reflected in all unbroken prefab instances across all scenes.
要创建可重复使用的背景画布预制游戏对象,请完成以下步骤:
To create a reusable Background Canvas prefab GameObject, complete the following steps:
Now, we can start setting up the windows that will hold our animated text.
To create the text box windows that will display our text, complete the following steps:
图 10.34:文本画布检查器
Figure 10.34: The Text Canvas Inspector
图 10.35: TextHolder1 及其子项
请注意,在上面的屏幕截图中,Text子项的 Rect Transform 不会完全延伸到TextHolder1的图像。这样,文本就不会越过窗口的白色区域。
Figure 10.35: TextHolder1 and its children
Note that in the preceding screenshot, the Text child’s Rect Transform does not stretch all the way across the image of TextHolder1. That way, the text won’t cross over the white area of the window.
Now, we’re ready to start animating our text!
现在我们有了布局设置完成后,我们可以为文本添加动画。为此,我们需要创建一个新脚本。该脚本将控制文本的动画,并在显示完所有文本后加载下一个场景。
Now that we have our layout set up, we can animate our text. To do this, we will need to create a new script. This script will control the animation of the text, as well as load the next scene after all the text has been displayed.
要创建看起来像是打字的动画文本,请完成以下步骤:
To create animated text that looks like it’s typing out, complete the following steps:
使用 UnityEngine;
使用 UnityEngine.UI;
[系统.可序列化]
公共类对话框
{
公共 CanvasGroup 文本持有者;
公共文本textDisplayBox;
公共字符串对话;
}我使用了[System.Serializable],这样我们就能在 Inspector 中看到这些值。请记住,每当我们使用Text类型时,我们都需要使用UnityEngine.UI命名空间。
using UnityEngine;
using UnityEngine.UI;
[System.Serializable]
public class DialogueBox
{
public CanvasGroup textHolder;
public Text textDisplayBox;
public string dialogue;
}I’ve used [System.Serializable] so that we will be able to see these values in the Inspector. Remember we need to use the UnityEngine.UI namespace whenever we use a Text type.
使用系统; 使用System.Collections; 使用 System.Collections.Generic; 使用 UnityEngine; 使用 UnityEngine.SceneManagement;
using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement;
公共列表<DialogueBox> dialogBoxes;
public List<DialogueBox> dialogueBoxes;
[SerializeField] 字符串 nextScene;
我用SerializeField属性标记它,以便它可以通过 Inspector 分配,同时仍然是私有的。
[SerializeField] string nextScene;
I marked it with the SerializeField attribute so that it can be assigned via the Inspector while still being private.
图 10.36:对话系统组件
Figure 10.36: The Dialogue System component
图 10.37:包含两个元素的对话系统组件
Figure 10.37: The Dialogue System component with two Elements
图 10.38:已填写所有属性的对话系统组件
Figure 10.38: The Dialogue System component with all its properties filled out
int哪个文本 = 0; int 字符串中的位置 = 0;
请注意,这两个变量不是公共的或序列化的,因此无法在 Inspector 中调整。它们的值将由脚本调整。whichText变量将允许我们在显示对话列表中的第一个字符串和第二个字符串之间切换。我们将编写的代码将很容易扩展到更多字符串。positionInString变量将跟踪动画正在输入哪个字符。我们希望跟踪这一点,以便我们可以判断用户是否正在加速文本,或者他们是否已经阅读了整个文本并只想继续下一部分。
int whichText = 0; int positionInString = 0;
Note that these two variables are not public or serialized and thus cannot be adjusted in the Inspector. Their values will be adjusted by the script. The whichText variable will allow us to switch between displaying the first string in the dialogue list and the second. The code we will write will easily be extendable to more strings. The positionInString variable will keep track of which character is being typed out by the animation. We want to keep track of this so that we can tell whether the text is being sped up by the user or if they have already read the whole text and just want to proceed to the next part.
协同程序textPusher;
Coroutine textPusher;
IEnumerator 写入文本()
{
对于(int i = 0; i <= dialogBoxes[whichText].dialogue.Length; i++)
{
对话框[whichText].textDisplayBox.text = 对话框[whichText].对话.Substring(0, i);
位置在字符串++;
产生返回新的WaitForSeconds(0.1f);
}
}此代码使用whichText变量在当前对话框中查找当前字符串,并循环遍历其所有字符。在循环的每个步骤中,UI Text 对象的 text 属性都会更新以显示字符串的前i 个字符,其中i表示循环的当前步骤。然后,它增加positionInString变量并等待十分之一秒,然后继续循环的下一步以显示下一个字符。
IEnumerator WriteTheText()
{
for (int i = 0; i <= dialogueBoxes[whichText].dialogue.Length; i++)
{
dialogueBoxes[whichText].textDisplayBox.text = dialogueBoxes[whichText].dialogue.Substring(0, i);
positionInString++;
yield return new WaitForSeconds(0.1f);
}
}This code finds the current string in the current dialogue box using the whichText variable and loops through all of its characters. With each step of the loop, the text property of the UI Text object is updated to display the first i characters of the string, where i represents the current step of the loop. It then increases the positionInString variable and waits a tenth of a second to display the next character by proceeding to the next step in the loop.
无效开始()
{
textPusher = StartCoroutine(WriteTheText());
}如果你现在玩游戏,你应该会看到场景中出现“Hello there!”文本。
void Start()
{
textPusher = StartCoroutine(WriteTheText());
}If you play your game now, you should see the Hello there! text type out within your scene.
公共无效ProceedText()
{
如果(positionInString < dialogBoxes [whichText] .dialogue.Length)
{
停止协同程序(textPusher);
对话框[哪个文本].textDisplayBox.文本 = 对话框[哪个文本].对话;
positionInString = dialogBoxes[whichText].dialogue.Length;
}
别的
{
切换CanvasGroup(dialogueBoxes[whichText].textHolder,false);
哪个文本++;
如果(whichText> = dialogBoxes.Count)
{
场景管理器.加载场景(下一个场景);
}
别的
{
字符串中的位置 = 0;
切换CanvasGroup(dialogueBoxes[whichText].textHolder,true);
textPusher = StartCoroutine(WriteTheText());
}
}
}
公共无效ToggleCanvasGroup(CanvasGroup面板,bool显示)
{
Panel.alpha = 显示?1:0;
面板.interactable = 显示;
面板.blocksRaycasts = 显示;
}当按钮按下后,代码首先使用positionInString变量确定是否已显示整个字符串。如果positionInString变量小于当前字符串中的字符总数,则显示完整字符串;否则,继续。
当positionInString变量小于当前字符串中的字符总数时,协程将使用StopCoroutine(textPusher)提前停止。
textDisplayBox的text属性被更新以显示完整的字符串,并且positionInString被设置为字符串的长度;这样,如果再次单击该按钮,此函数就会知道它可以继续下一步。
当positionInString变量不小于当前字符串中的字符总数时,当前 Canvas Group 将被停用,然后whichText变量将增加。一旦此变量增加,代码将检查是否还有更多文本框需要动画。如果没有,则加载下一个场景。如果有更多文本框需要动画,则positionInString变量将重置为0,因此将首先显示字符串中的第一个字符。现在激活了新的 Canvas Group,并重新分配了textPusher变量,以便再次播放协程循环。
public void ProceedText()
{
if (positionInString < dialogueBoxes[whichText].dialogue.Length)
{
StopCoroutine(textPusher);
dialogueBoxes[whichText].textDisplayBox.text = dialogueBoxes[whichText].dialogue;
positionInString = dialogueBoxes[whichText].dialogue.Length;
}
else
{
ToggleCanvasGroup(dialogueBoxes[whichText].textHolder, false);
whichText++;
if (whichText >= dialogueBoxes.Count)
{
SceneManager.LoadScene(nextScene);
}
else
{
positionInString = 0;
ToggleCanvasGroup(dialogueBoxes[whichText].textHolder, true);
textPusher = StartCoroutine(WriteTheText());
}
}
}
public void ToggleCanvasGroup(CanvasGroup Panel, bool show)
{
Panel.alpha = show ? 1 : 0;
Panel.interactable = show;
Panel.blocksRaycasts = show;
}When a button is pressed, the code first determines whether the whole string has been displayed using the positionInString variable. If the positionInString variable is smaller than the total characters in the current string, it displays the complete string; otherwise, it proceeds.
When the positionInString variable is less than the total characters in the current string, the coroutine is stopped early with StopCoroutine(textPusher).
The text property of the textDisplayBox is updated to display the full string, and the positionInString is set to the length of the string; that way, if the button is clicked again, this function will know that it can proceed to the next step.
When the positionInString variable is not less than the total characters in the current string, the current Canvas Group is deactivated, and then the whichText variable is increased. Once this variable is increased, the code checks whether any more text boxes need animating. If there are not, the next scene loads. If more text boxes need animating, the positionInString variable is reset to 0, so the very first character in the string will be displayed first. The new Canvas Group is now activated, and the textPusher variable is reassigned so that the coroutine loop will play again.
为了改进这一点,你还可以创建TextHolder对象的预制件,并编写基于Dialogue List实例化到场景中的代码。我建议实施这一改变如果你要制作一个更复杂的对话系统。
To improve on this, you can also create a prefab of the TextHolder object and write code that instantiates into the scene based on Dialogue List. I recommend implementing this change if you will be making a more complicated dialogue system.
笔记
Note
本书代码包中提供的代码示例包括代码注释,由于太杂乱,无法显示,因此未在此处显示在这篇文章中。
The code example provided in this book’s code bundle includes code comments not shown here as it was too cluttered to display in this text.
让我们扩展一下我们的动画文本示例,以便包含翻译。这是一个基本示例,用于演示如何访问文本组件的某些属性,其架构方式不一定适合大型项目。
Let’s expand upon our animated text example so that it includes translations. This is a basic example to demonstrate how to access certain properties of Text components and isn’t necessarily architected in a way that would be sustainable for a large project.
笔记
Note
请注意,我使用谷歌翻译来获得这些翻译,因此它们可能不完全准确:
Please note that I used Google Translate to obtain these translations, so they may not be fully accurate:
图 10.39:谷歌翻译
Figure 10.39: Google Translate
To add translation to the animated text example we completed previously, complete the following steps:
使用 System.Collections.Generic;
using System.Collections.Generic;
[系统.可序列化]
公共课翻译
{
公共字符串语言键;
公共字符串翻译字符串;
公共字体字体;
公共字体样式字体样式;
}languageKey字符串将用作查找适当翻译的关键字。
[System.Serializable]
public class Translation
{
public string languageKey;
public string translatedString;
public Font font;
public FontStyle fontStyle;
}The languageKey string will be used as a key to finding the appropriate translation.
公共列表<Translation>翻译;
您的DialogueSystem组件现在应更新为如下所示:
public List<Translation> translations;
Your DialogueSystem component should now be updated to look like the following:
图 10.40:带有翻译的对话系统组件
Figure 10.40: The Dialogue System component with translations
图 10.41:对话框中填写的翻译键
Figure 10.41: The translation keys filled out on the Dialogue Boxes
钥匙 | 你好呀! | 我是一只猫,出于某种原因,我正在收集食物! |
西文 | 你好! | Soy un gato y, por alguna razón, estoy recolectando comida! |
贾 | 谢谢你! | 私は猫で、なぜか食べ物を集めています! |
中文 | 你好呀! | 我是一只猫,由于某种原因,我正在收集食物! |
柯 | 好的! | 亲爱的,我的! |
表 10.5:要输入的翻译字符串
Key | Hello there! | I’m a cat and, for some reason, I’m collecting food! |
es | ¡Hola! | ¡Soy un gato y, por alguna razón, estoy recolectando comida! |
ja | こんにちは! | 私は猫で、なぜか食べ物を集めています! |
zh | 你好呀! | 我是一只猫,出于某种原因,我正在收集食物! |
ko | 안녕! | 나는 고양이고, 왠지 모를 음식을 모으고 있다! |
Table 10.5: The translation strings to enter
You’ll notice that the Unity Engine is capable of rendering these languages in the Inspector.
图 10.42:以两种字体呈现韩文文本的对话框
因此,我们希望每当需要翻译时,就将字体更改为我们专门选择的字体。我将使用ZCOOL KuaiLe字体进行简体中文翻译,并使用RocknRoll One进行所有其他翻译。
从以下位置下载字体并将其添加到您的Assets/Fonts文件夹:
Figure 10.42: The dialogue box with the Korean text rendering in two fonts
Therefore, we’ll want to change the font whenever the translation occurs to one that we specifically choose. I will use the ZCOOL KuaiLe font for the Simplified Chinese translation and RocknRoll One for all the others.
Download the fonts from the following locations and add them to your Assets/Fonts folder:
图 10.43:所有属性均已完成的两个对话框
我已经包含了更改字体样式的选项,但在这个例子中,我将把它们全部保留为“正常” 。
Figure 10.43: The two dialogue boxes with all properties completed
I’ve included the option to change the font style, but I’m going to leave them all at Normal for this example.
[SerializeField] 字符串当前语言;
[SerializeField] string currentLanguage;
私有无效翻译()
{
foreach(DialogueBox dialogBox 在 dialogBoxes 中)
{
int 索引 = dialogBox.translations.FindIndex(x => x.languageKey == currentLanguage);
如果(索引 >= 0)
{
dialogueBox.dialogue = dialogueBox.translations[index].translatedString;
dialogBox.textDisplayBox.font = dialogBox.翻译[index].font;
dialogBox.textDisplayBox.fontStyle = dialogBox.翻译[index].fontStyle;
}
}
}此方法将查找哪些翻译具有currentLanguage变量指定的键。然后它将dialog变量、font和fontStyle更改为适当的值。如果在任何languageKey中都找不到currentLanguage变量,则index将等于-1,并且不会实施任何更改。
private void Translate()
{
foreach (DialogueBox dialogueBox in dialogueBoxes)
{
int index = dialogueBox.translations.FindIndex(x => x.languageKey == currentLanguage);
if (index >= 0)
{
dialogueBox.dialogue = dialogueBox.translations[index].translatedString;
dialogueBox.textDisplayBox.font = dialogueBox.translations[index].font;
dialogueBox.textDisplayBox.fontStyle = dialogueBox.translations[index].fontStyle;
}
}
}This method will find which of the translations have the key designated by the currentLanguage variable. It will then change the dialogue variable, the font, and the fontStyle to the appropriate values. If the currentLanguage variable is not found in any of the languageKeys, index will equal -1 and no changes will be implemented.
无效唤醒()
{
翻译();
}void Awake()
{
Translate();
}图 10.44:西班牙语翻译被截断
Figure 10.44: The Spanish translation getting cut off
在层次结构中选择两个文本对象,然后从它们的文本组件中选择最佳适合设置。
Select both Text objects in the Hierarchy and then select the Best Fit setting from their Text components.
在此示例中,使用最佳适配的一个缺点是字体大小会随着文本动画而变化。这并不理想。但是,为了保持文本框大小,我们目前没有太多选择!在第 12 章中学习滚动矩形和蒙版后,我们可以在滚动时保持字体大小通过文本。
A downside to using Best Fit in this example is the font size changes as the text animates. It’s not ideal. But, to maintain the text box size, we didn’t have much choice – for now! After we learn about Scroll Rects and Masks in Chapter 12, we can maintain the font size while scrolling through the text.
让我们远离暂时构建我们的场景,以探索制作自定义字体。我们不会在我们一直在处理的场景中使用这种自定义字体,但创建自定义字体的过程仍然很重要。我们将使用以下精灵创建一个显示数字 0 到 9 的自定义字体:
Let’s take a step away from building out our scenes for a moment to explore making a custom font. We won’t be using this custom font in the scenes we’ve been working on, but the process of creating a custom font is still important to cover. We’ll create a custom font that displays the numbers 0 through 9 using the following sprites:
图 10.45:我们将要创建的自定义字体
Figure 10.45: The custom font we will create
用于创建的精灵字体是根据https://opengameart.org/content/shooting-gallery上的免费艺术资产修改的。
The sprites used to create the font are modified from the free art asset found at https://opengameart.org/content/shooting-gallery.
为了给这个字体创建一个均匀分布的精灵表,我使用了 TexturePacker 程序和 Photoshop。这个过程可以完全用 Photoshop 等照片编辑软件来完成,但是TexturePacker 简化了该过程。TexturePacker 可在https://www.codeandweb.com/texturepacker找到。
To create an evenly spaced sprite sheet for this font, I used the TexturePacker program, along with Photoshop. The process can be done entirely with a photo editing software such as Photoshop, but TexturePacker simplifies the process. TexturePacker can be found at https://www.codeandweb.com/texturepacker.
创建自定义字体的过程非常耗时,而且有点麻烦。要创建自定义字体,您必须为计划使用该字体渲染的每个字符输入坐标位置,因此除了数字或非常有限的字符集之外,我不建议使用它。
The process of creating a custom font is time-consuming and kind of a pain. To create a custom font, you have to put in coordinate locations for each character you plan on rendering with the font, so I don’t recommend it for anything other than numbers or a very limited character set.
如果您想要具有更强大字符集的自定义字体,请查看 Unity 资源商店以获取简化位图字体流程的各种选项。
If you want a custom font with a more robust character set, check out the Unity asset store for various options on streamlining the bitmap font process.
要创建上图显示的自定义字体,请完成以下步骤:
To create the custom font displayed in the preceding figure, complete the following steps:
图 10.46:自定义精灵表
手动创建时自定义字体,您的字符必须均匀分布。这将使您在输入单个字符的设置时更加轻松。您可以将精灵表的导入设置保留为默认的精灵(2D 和 UI)纹理类型和单精灵模式。
Figure 10.46: The custom sprite sheet
When manually creating a custom font, your characters must be spaced evenly. This will make your life significantly easier while entering the settings of the individual characters. You can leave the sprite sheet’s import settings at the defaults of Sprite (2D and UI) Texture Type and Single Sprite Mode.
图 10.47:自定义字体材质
Figure 10.47: The custom font material
图 10.48:自定义字体材质已更新
Figure 10.48: The custom font material updated
图 10.49:自定义字体的字符矩形
Figure 10.49: The Character Rects of the custom font
图 10.50:计算 UV W 和 UV H 值
总共有五列字符。因此,每个精灵占据精灵宽度的五分之一。五分之一等于 20% 或小数 0.2。我们需要在UV W插槽中输入0.2。如果你不擅长百分比,那也没关系。Unity 将为您执行计算!在UV W插槽中输入1/5并按Enter将自动计算小数0.2。
总共有两行字符。因此,每个精灵占据精灵高度的一半;一半等于 50% 或小数 0.5。在UV H槽中输入1/2并按Enter键将自动计算出槽中正确的小数0.5 。
Figure 10.50: Calculating the UV W and UV H values
There are a total of five columns of characters. Therefore, each sprite takes up one-fifth of the width of the sprite. One-fifth is equal to 20% or 0.2 as a decimal. We need to put 0.2 in the UV W slot. If you’re not great with percentages, that’s fine. Unity will perform the calculation for you! Typing 1/5 in the UV W slot and pressing Enter will automatically compute the 0.2 decimal.
There are a total of two rows of characters. Therefore, each sprite takes up one-half of the height of the sprite; one-half is equal to 50% or 0.5 as a decimal. Typing 1/2 in the UV H slot and pressing Enter will automatically compute the correct decimal of 0.5 in the slot.
完成步骤 9到11后,你的元素 0角色应具有以下属性:
After completing Steps 9 through 11, your Element 0 character should have the following properties:
图 10.51:元素 0 及其值已填入
Figure 10.51: Element 0 with its values filled in
图 10.52:元素 0 至元素 10 的重复
Figure 10.52: Element 0 duplicated through Element 10
因此,我们应该进入元素 0至元素 9中的索引值48至57 :
元素 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
指数 | 四十八 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
Therefore, we should enter the Index values of 48 through 57 in Element 0 through Element 9:
Element | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Index | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
表10.6:各元素的索引值
Table 10.6: The index values of each element
图 10.53:精灵表的行和列
要计算UV X和UV Y值,请使用以下公式:
图 10.54:计算 UV X 和 UV Y
请记住,由于我们的角色是均匀分布的,我们的UV W和UV H值对于每个角色都是相同的。
因此,如果我们查看元素 0,则可以通过将 0 (代表第 0 列)乘以 0.2 (UV W值)得到0来找到其UV X值。可以通过将 1 (代表第 1 行)乘以 0.5 (UV H 值)得到0.5来找到其UV Y值。
下表表示应为每个字符输入的UV X和UV Y值:
|
元素 |
紫外线 |
紫外线 |
|
0 |
0 |
0.5 |
|
1 |
0.2 |
0.5 |
|
2 |
0.4 |
0.5 |
|
3 |
0.6 |
0.5 |
|
4 |
0.8 |
0.5 |
|
5 |
0 |
0 |
|
6 |
0.2 |
0 |
|
7 |
0.4 |
0 |
|
8 |
0.6 |
0 |
|
9 |
0.8 |
0 |
表 10.7:字体的 UV X 和 UV Y 属性
下图更直观地表示了坐标模式:
图 10.55:精灵表的坐标
Figure 10.53: The rows and columns of the sprite sheet
To calculate the UV X and UV Y values, use the following formulas:
Figure 10.54: Calculating UV X and UV Y
Remember that since our characters are evenly spaced, our UV W and UV H values are the same for each character.
So, if we look at Element 0, its UV X value can be found by multiplying 0 (for column 0) by 0.2 (the UV W value) to get 0. Its UV Y value can be found by multiplying 1 (for row 1) by 0.5 (the UV H value) to get 0.5.
The following chart represents the UV X and UV Y values that should be entered for each character:
|
Element |
UV X |
UV Y |
|
0 |
0 |
0.5 |
|
1 |
0.2 |
0.5 |
|
2 |
0.4 |
0.5 |
|
3 |
0.6 |
0.5 |
|
4 |
0.8 |
0.5 |
|
5 |
0 |
0 |
|
6 |
0.2 |
0 |
|
7 |
0.4 |
0 |
|
8 |
0.6 |
0 |
|
9 |
0.8 |
0 |
Table 10.7: The UV X and UV Y properties of the font
The following figure provides a more visual representation of the coordinate pattern:
Figure 10.55: The coordinates of the sprite sheet
图 10.56:步骤 15 后应显示的自定义字体
Figure 10.56: The custom font as it should be displayed after Step 15
图 10.57:调整一些属性后的自定义字体
Figure 10.57: The custom font after adjusting some properties
现在我们已经完成了导入我们的自定义字体,让我们看看进一步调整它。
Now that we’ve completed importing our custom font, let’s look at adjusting it further.
在我们的例子中,所有数字间距均匀字体大小无法可以更改。您可能希望数字更接近或字体大小不同。让我们更改自定义字体,使精灵更接近。下图显示了调整后的字体与我们在本示例的最后一部分中创建的原始字体的对比:
In our example, all the numbers are evenly spaced and the font size cannot be changed. You will likely want your numbers to be closer together or of a different font size. Let’s alter our custom font so that the sprites are closer together. The following figure shows our font after the adjustments versus the original we created in the last part of this example:
图 10.58:调整间距后的自定义字体和不调整间距后的自定义字体
Figure 10.58: The custom font with and without spacing adjustment
要更改字符间距,请完成以下步骤:
To change the spacing of the characters, complete the following steps:
图 10.59: 1 个字形间距问题
Figure 10.59: The 1 glyph spacing problem
笔记
Note
您可以在预览场景中的结果时更改自定义字体的设置。要使更改自定义字体后文本在场景中刷新,您必须先保存场景。
You can change the settings on your custom font while previewing the results in the scene. To get the text to refresh in the scene after making a change to your custom font, you have to save the scene first.
元素 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
进步 | 三十八 | 三十 | 三十五 | 三十五 | 三十五 | 三十五 | 三十五 | 三十五 | 三十八 | 三十五 |
Element | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
Advance | 38 | 30 | 35 | 35 | 35 | 35 | 35 | 35 | 38 | 35 |
表 10.8:自定义字体的高级属性
Table 10.8: Advance properties of the custom font
图 10.60:显示字体的所有字形
Figure 10.60: All the glyphs of the font displayed
图 10.61:0 字形上的不同高级设置
Figure 10.61: Different Advance settings on the 0 glyph
现在,如果您想调整字体大小,则无法通过更改Text 组件的Font Size属性来更改自定义字体的大小;更改Font Size不会产生任何效果。要更改字体大小,必须更改Rect Transform 组件的Scale X和Scale Y属性。要使字体大小减半,请将Scale X和Scale Y更改 为0.5。
Now, if you’d like to adjust the font size, you cannot change the size of a custom font by changing the Font Size property of the Text component; changing the Font Size will do nothing. To change the size of the font, you must change the Scale X and Scale Y properties of the Rect Transform component. To get the font half the size, change Scale X and Scale Y to 0.5.
正如我提到的早些时候,我们可能不会使用要构建的字体我们的主要示例场景,但创建自定义字体的过程仍然是一个有用的练习。现在,让我们继续创建一些其他常见的 UI 资产——健康条和进度条。
As I mentioned earlier, we probably won’t be using this font to build our master example scene, but the process of creating a custom font is still a useful exercise. Now, let’s move on to creating some other common UI assets – health bars and progress bars.
我们的暂停面板的横幅看起来目前有点空。现在我们已经介绍了如何使用 TextMeshPro - Text,我们可以创建一个漂亮的曲线文本,带有渐变,与横幅很好地对齐:
The banner of our Pause Panel looks a bit bare currently. Now that we’ve covered using TextMeshPro - Text, we can create a nice curved text with a gradient that lines up well with our banner:
图 10.62:带有渐变和扭曲的横幅文本
Figure 10.62: The banner text with a gradient and warp
To create the curved text shown in the preceding screenshot, complete the following steps:
图 10.63:横幅文本的放置
Figure 10.63: The banner text’s placement
图 10.64:使用滴管工具获取文本颜色
Figure 10.64: Using the eye dropper tool to get the text color
图 10.65:颜色渐变属性完成
Figure 10.65: The Color Gradient properties completed
图 10.66:Warp Text 示例组件
Figure 10.66: The Warp Text Example component
图 10.67:为顶点曲线选择一条平坦曲线
Figure 10.67: Selecting a flat curve for the Vertex Curve
图 10.68:顶点曲线的最终版本
Figure 10.68: The final version of the Vertex Curve
图 10.69:包含所有适当场景的构建设置
您可以通过右键单击并删除不需要的场景来删除它们。
Figure 10.69: The Build Settings with all the appropriate scenes
You can remove unnecessary scenes by right-clicking on them and removing them.
笔记
Note
我想指出的是,虽然我在每个新章节中复制和重命名每个场景,但您不必这样做。我这样做是为了维护一个易于查看的进度日志,以了解本书场景的进展情况,但不可否认的是,如果您也这样做,您的项目可能会变得有点混乱。所以,如果你想知道“为什么我不能一直添加场景,而不是每次都开始一个新场景?”你可以!
I’d like to point out that, while I am duplicating and renaming each scene with each new chapter, you do not have to do so. I am doing it to maintain an easy-to-view progression log of what is happening to our scenes for this book, but admittedly, your project is probably getting a bit cluttered if you’ve been doing it, too. So, if you are wondering “Why can’t I just keep adding to my scenes instead of starting a new one each time?” you can!
And that’s it for creating a wrapped text using TextMeshPro!
哇!我敢打赌,当本章开始时,你一定想过,关于文本,你到底能说多少?没想到这是迄今为止最长的一章!我差点通过添加更多示例使其更长!唉,尽管我想提供更多示例,但我必须遵守页数限制——尽管我已经超过了这个限制。如果您渴望获得有关 UI Text 和 Text-TextMeshPro 的更多示例,我强烈建议您查看本章中链接的各种示例,以及您使用 TextMesh – Pro 示例下载的示例场景。
Wow! I bet you thought, how much can you really say about text? when this chapter started and didn’t expect to be faced with the longest chapter so far! And I almost made it longer by adding more examples! Alas, as much as I would like to provide even more examples, I have a page limit I have to adhere to – even though I have already blown past it. If you are hankering for even more examples concerning UI Text and Text-TextMeshPro, I strongly recommend you review the various examples linked within this chapter, as well as the example scenes that you downloaded with the TextMesh – Pro examples.
在下一章中,我们将深入研究 UI 图像和效果。
In the next chapter, we’ll do a deep dive into UI Images and Effects.
我们在前面的章节中已经使用了 UI 图像,但现在我们将进一步了解组件的特定属性,以及如何通过代码访问组件。我们还将介绍一些可以应用于 UI 对象以增加视觉吸引力的 UI 效果组件。虽然我们将彻底研究这些组件,但本章的大部分内容将重点介绍视频游戏(尤其是移动视频游戏)中 UI 功能的具体示例。
We’ve worked with UI Images in the previous chapters, but now we’ll learn more about the component’s specific properties, as well as how to access the component via code. We’ll also look at some of the UI effect components that we can apply to our UI objects for visual appeal. While we will look at the components thoroughly, the majority of this chapter focuses on specific worked-out examples of UI functionality that you will find in video games, particularly mobile video games.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
笔记
Note
示例部分之前的部分中显示的所有示例都可以在代码包中提供的 Unity 项目中找到。它们可以在标记为Chapter11 的场景中找到。
All the examples shown in the sections before the Examples section can be found within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter11.
每个示例图都有一个标题,说明场景中的示例名称。
Each example figure has a caption stating the example name within the scene.
在场景中,每个示例都在其自己的画布上,并且一些画布处于停用状态。要查看停用画布上的示例,只需在检查器中选中画布名称旁边的复选框即可。每个画布还具有自己的事件系统。如果您一次激活多个画布,这将导致错误。
In the scene, each example is on its own Canvas, and some of the Canvases are deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector. Each Canvas is also given its own Event System. This will cause errors if you have more than one Canvas activated at a time.
您可以在此处找到本章节的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2011
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2011
我们之前已经创建过 UI 图像,但是让我们看看它的属性和组成部分。
We’ve created a UI Image before, but let’s look at its properties and components.
您可以使用+ | UI | Image创建一个新的 UI Image 对象。
You can create a new UI Image object using + | UI | Image.
UI Image对象包含Rect Transform和Canvas Renderer组件以及Image组件。我们已经详细了解了Rect Transform和Canvas Renderer组件;现在,让我们看看Image组件。
The UI Image object contains the Rect Transform and Canvas Renderer components as well as the Image component. We’ve looked at the Rect Transform and Canvas Renderer components extensively; now, let’s look at the Image component.
Image组件上的第一个设置是Source Image属性,它表示将要渲染的精灵。Color属性表示要渲染的精灵的基本颜色。将颜色保留为白色将使图像看起来与精灵完全一样,但更改颜色将为图像添加有色叠加。您还可以通过降低 alpha 值来更改图像的透明度。Material 属性允许您向图像添加材质。
The first setting on the Image component is the Source Image property, which represents the sprite that will be rendered. The Color property represents the base color of the sprite being rendered. Leaving the color at white will make the Image appear exactly as the sprite, but changing the color will add a tinted color overlay to the Image. You can also change the transparency of the Image by reducing the alpha value. The Material property allows you to add a material to the Image.
Raycast Target和Raycast Padding属性的工作方式与Text组件相同,通过指定图像是否会阻止点击其后面的 UI 对象以及是否有任何填充来阻止点击。Maskable属性确定图像是否会受到遮罩的影响。
The Raycast Target and Raycast Padding properties work the same way they do on the Text component, by specifying whether the Image will block clicks on UI objects behind it or not and if there is any padding to the block. The Maskable property determines if the Image can be affected by masks or not.
当将精灵分配到“源图像”插槽后, “图像类型”下的图像组件中会出现新的选项,如下图所示:
When a sprite is assigned to the Source Image slot, new options appear in the Image component under Image Type, as shown in the following figure:
图 11.1:UI 图像组件及其所有属性
Figure 11.1: The UI Image component and all its properties
Let’s look at the various options for Image Type and how they affect a sprite.
图像类型属性决定Source Image指定的精灵会以何种方式呈现。一共有四个选项:Simple、Sliced、Tiled和Filled。我们来看看它们。
The Image Type property determines how the sprite specified by Source Image will appear. There are four options: Simple, Sliced, Tiled, and Filled. Let’s take a look at them.
图像类型属性设置为“简单”的图像会在整个精灵中均匀缩放。这是默认类型。选择“简单”时作为图像类型,标记为使用精灵网格的切换开关,标记为保留纵横比的切换开关和标记为设置原生大小的 按钮变为可用。
An Image with its Image Type property set to Simple scales evenly across the sprite. This is the default type. When Simple is selected as the Image Type, a toggle labeled Use Sprite Mesh, a toggle labeled Preserve Aspect, and a button labeled Set Native Size become available.
选择使用 Sprite Mesh切换按钮将使图像使用TextureImporter创建的 Sprite Mesh。默认情况下,此属性处于取消选择状态,并且 Sprite 的网格是四边形。如果您不希望图像由矩形表示,而是希望它具有紧密贴合图像可见区域的网格,则需要选择此属性。
Selecting the Use Sprite Mesh toggle will have the Image use the sprite mesh created by the TextureImporter. By default, this property is deselected, and the sprite’s mesh is a quad. You will select this property if you don’t want the Image represented by a rectangle, but instead want it to have a mesh that fits tightly around the visible area of the Image.
勾选“保留纵横比”属性后,精灵将保留其部分显示,可能不会填满矩形变换的整个区域。选择此属性可确保您的精灵看起来与原先预期一致,不会拉伸。
When the Preserve Aspect property is checked, the sprite will display with its portions preserved and may not appear to fill the entire area of the Rect Transform. Selecting this property ensures that your sprites look as originally intended and are not stretched out.
选择“设置原始尺寸”按钮将图像的尺寸设置为spri的像素尺寸德。
Selecting the Set Native Size button sets the dimensions of the Image to the pixel dimensions of the sprite.
切片图像被分成九个区域。当切片图像被缩放时,图像的所有区域都会被缩放,除了角落。这允许您缩放图像而不扭曲其角。此功能特别适用于具有圆角的精灵,您希望能够将其拉伸为圆角矩形。
Sliced Images are split into nine areas. When a Sliced Image is scaled, all areas of the Image are scaled, except the corners. This allows you to scale an Image without distorting its corners. This works particularly well with sprites that have rounded corners that you want to be able to stretch into rounded rectangles.
当图像设置为切片时,将出现填充中心和每单位像素乘数属性。下图显示了一个圆角矩形,其中有五个替代版本正在拉伸。您可以看到,选择切片如何允许圆角矩形以保持圆角矩形形状的方式拉伸,而将图像类型保留为简单会导致图像在缩放时边缘变形。
When an image is set to Sliced, the Fill Center and Pixels Per Unit Multiplier properties appear. The following figure shows a rounded rectangle with five alternate versions of it being stretched. You can see how selecting Sliced allows the rounded rectangle to stretch in a way that maintains the rounded rectangle shape while leaving the Image Type at Simple causes a distortion in the edges of the Image when it is scaled.
Figure 11.2: Sliced Image Type Example in the Chapter11 scene
您必须在 Sprite 或 Sprite Sheet 的Sprite Editor中指定这九个区域的位置。如果您尚未指定区域,则图像组件中将显示一条消息。
You must specify where the nine areas will be, within the Sprite Editor of the sprite or sprite sheet. If you have not specified the regions, a message will appear within the Image component.
要在Sprite 编辑器中指定区域,您需要将精灵边缘的绿色框拖到所需位置。从以下屏幕截图中可以看到,您需要将绿线,这样它们就不会围绕边缘的曲线了:
To specify the area in the Sprite Editor, you need to drag the green boxes on the edges of the sprite to the desired position. As you can see from the following screenshot, you want to drag the green lines so that they stop surrounding the curves of the edges:
图 11.3:在 Sprite Edi 中指定精灵的九个区域托尔
Figure 11.3: Specifying the nine areas of a sprite in the Sprite Editor
接下来我们来谈谈Image Type下的Tiled选项。
Next, let’s talk about the Tiled option under the Image Type.
选择“平铺”作为图像类型将导致要重复的图像以填充拉伸区域。下图演示了选择“简单”和“平铺”作为“图像类型”如何影响缩放的图像:
Selecting Tiled for Image Type will cause the Image to repeat to fill the stretched area. The following figure demonstrates how selecting Simple and Tiled for Image Type affects the scaled Images:
图 11.4:第 11 章场景中的平铺图像类型示例
Figure 11.4: Tiled Image Type Example in the Chapter11 scene
Next, let’s talk about the Filled option under the Image Type.
图像类型选择为填充的图像将填充精灵的百分比,从指定方向的原点开始。精灵中超过指定百分比的任何部分都不会被渲染。选择“填充”后,将显示新属性:
Images with Filled selected for their Image Type will fill in a percentage of the sprite, starting at an origin in a specified direction. Any part of the sprite past the designated percentage will not be rendered. When Filled is selected, new properties are displayed:
图 11.5:填充图像的属性
Figure 11.5: Properties for a Filled Image
填充方法属性决定精灵是水平、垂直还是径向填充。有五个选项:水平、垂直、径向 90、径向 180和径向 360。每个填充方法选项都将从填充原点开始绘制精灵,直到填充量。当选择其中一种径向方法时,您还可以选择让填充按顺时针方向进行;如果您选择不选择此选项,则图像将按逆时针方向填充。下图演示了三种填充方法选项,所有填充量值均为0.75%或75%:
The Fill Method property determines whether the sprite will be filled horizontally, vertically, or radially. There are five options: Horizontal, Vertical, Radial 90, Radial 180, and Radial 360. Each of these Fill Method options will begin drawing the sprite at the Fill Origin up to the Fill Amount. When one of the radial methods is selected, you can also select the option to have the fill progress Clockwise; if you choose not to select this option, the Image will fill counterclockwise. The following figure demonstrates the three Fill Method options, all with Fill Amount values of 0.75 or 75 percent:
图 11.6:第 11 章场景中的填充图像类型示例
Figure 11.6: Filled Image Type Example in the Chapter11 scene
当您看到水平和垂直 填充方法选项的实际作用时,它们的含义就不言自明了,但仅从外观上判断这三种径向方法的工作原理则有些困难。径向 90将径向中心置于一个角上,径向 180将径向中心置于一个边上,径向 360将径向中心置于精灵的中心。
The Horizontal and Vertical Fill Method options are somewhat self-explanatory when you see them in action, but it’s a little more difficult to determine exactly how the three radial methods work just from looking at them. Radial 90 places the center of the radial at one of the corners, Radial 180 places the center of the radial at one of the edges, and Radial 360 places the center of the radial in the center of the sprite.
填充 图像类型选项还具有设置原始大小属性。
The Filled Image Type option also has the Set Native Size property.
现在我们已经探索了 UI 图像组件,我们可以看看一些UI效果组件科目。
Now that we’ve explored the UI Image component, we can look at some UI effect components.
三种效果组件可让您添加特殊效果添加到文本和图像对象中:阴影、轮廓和位置作为 UV1 。它们都可以在添加组件| UI |效果下找到。让我们分别看一下每一个,从阴影合成开始愛爾蘭。
Three effects components allow you to add special effects to your Text and Image objects: Shadow, Outline, and Position as UV1. They can all be found under Add Component | UI | Effects. Let’s look at each one individually, starting with the Shadow component.
The Shadow component adds a simple shadow to your Text or Image object.
图 11.7:阴影组件
Figure 11.7: The Shadow component
您可以使用“效果颜色”属性更改阴影的颜色和透明度。“效果距离”属性确定其相对于其所附加图形的位置。“使用图形 Alpha”属性将阴影的颜色与其所附加图形的颜色相乘。因此,如果选中此属性并降低原始图形的 Alpha(不透明度),则阴影的 Alpha 也会降低,结果阴影是两个 Alpha 值的乘积。但是,如果未选中“使用图形 Alpha”属性,则无论原始图形的 Alpha 如何,阴影都将保持其 Alpha 值。因此,如果您将原始图形的 Alpha 一直调低到0 ,使其不可见,则阴影将根据“效果颜色”属性上指定的 Alpha 保持可见。
You can change the color and transparency of the shadow with the Effect Color property. The Effect Distance property determines its position relative to the graphic to which it is attached. The Use Graphic Alpha property will multiply the color of the shadow with the color of the graphic on which the shadow is attached. So, if this property is checked and the alpha (opacity) of the original graphic is reduced, the alpha of the shadow will reduce as well, with the resulting shadow being a product of the two alpha values. However, if the Use Graphic Alpha property is unchecked, the shadow will maintain its alpha value regardless of the alpha of the original graphic. So, if you turned the alpha of the original graphics all the way down to 0, rendering it invisible, the shadow would remain visible based on the alpha specified on the Effect Color property.
下图显示了Shadow组件运行的一些示例:
The following figure shows a few examples of the Shadow component in action:
图 11.8:第 11 章场景中的阴影组件示例
Figure 11.8: Shadow Component Example in the Chapter11 scene
所有四根香蕉的阴影组件的“效果颜色”属性都设置了相同的 alpha 值。第一根香蕉的图像组件的颜色属性将 alpha 设置为完全不透明。第二根、第三根和第四根香蕉的图像组件的不透明度降低。第二根和第三根香蕉具有相同的属性,但第二根香蕉第一个使用了“使用图形 Alpha”属性,而第三个没有。因此,你可以看到,第三个香蕉的阴影并没有因为香蕉图像组件的变暗而变暗。第四个也是最后一个香蕉的图像组件的不透明度设置为0,但由于阴影组件上没有选择“使用图形 Alpha”,阴影并没有随着香蕉变暗,而是保持在指定的alpha 值价值。
All four bananas have the same alpha value set on their Shadow component’s Effect Color property. The Image component’s Color property of the first banana has the alpha set to full opacity. The second, third, and fourth bananas have the opacity of their Image component reduced. The second and third bananas have identical properties, except that the second banana uses the Use Graphic Alpha property and the third does not. So, you can see that the shadow of the third banana has not been dimmed by the dimming of the banana’s Image component. The fourth and final banana has its Image component’s opacity set to 0, but since Use Graphic Alpha is not selected on the Shadow component, the shadow did not dim with the banana and remains at its designated alpha value.
大纲组件模拟通过在指定距离处在图形周围创建四个阴影来在图形周围勾勒出轮廓。
The Outline component simulates an outline around the graphic by creating four shadows around it at specified distances.
图 11.9:大纲组件
Figure 11.9: The Outline component
Outline组件会根据Effect Distance X在原图形的左右两侧创建两个阴影,并根据Effect Distance Y在原图形的上下两侧创建两个阴影。与Shadow组件不同,这两个距离的正负值没有区别,因为每个轴创建的两个阴影是镜像的。
The Outline component will create two shadows to the left and right of the original graphic based on Effect Distance X and two shadows to the top and bottom of the original graphic based on Effect Distance Y. Unlike the Shadow component, there is no difference in a negative or positive value for these two distances because the two shadows created for each axis are mirrored.
将效果距离 X值设置为-3本质上只是切换了两个水平阴影的位置,但效果看上去是一样的,如下图所示:
Setting the Effect Distance X value to -3 essentially just switches the positions of the two horizontal shadows, but the effect looks the same, as shown in the following figure:
图 11.10:第 11 章场景中的大纲组件示例 1
Figure 11.10: Outline Component Example 1 in the Chapter11 scene
此组件上的“使用图形 Alpha”属性与阴影组件上的“使用图形 Alpha”属性完全相同,如下所示:
The Use Graphic Alpha property works identically on this component as it does on the Shadow component, as shown:
图 11.11:第 11 章场景中的大纲组件示例 2
Figure 11.11: Outline Component Example 2 in the Chapter11 scene
接下来我们看一下Position As UV1组件nent。
Next, let’s look at the Position As UV1 component.
Position As UV1组件允许您更改 Canvas 渲染的 UV 通道。如果您想创建利用烘焙光照贴图的自定义着色器,可以使用此功能。
The Position As UV1 component allows you to change the UV channel that the Canvas renders on. This is used if you want to create custom shaders that utilize baked light maps.
图 11.12:Position As UV1 组件
Figure 11.12: The Position As UV1 component
遗憾的是,自定义着色器是一个非常沉重的话题,超出了本文的范围,因此我不会进一步讨论该组件的使用。
Sadly, custom shaders are a pretty heavy topic and go past the scope of this text, so I won’t go any further into the usage of this component.
现在我们已经回顾了 UI 图像组件和一些 UI 效果组件,让我们看一些使用这些组件的方法的示例科目。
Now that we’ve reviewed the UI Image component and some UI effect components, let’s look at some examples of ways we can use these components.
在本章中,我们将通过添加一些新的 UI 元素来进一步扩展我们一直在构建的场景。我们还将研究一些移动/触摸屏 UI和交互。
In this chapter, we’ll expand on the scene we’ve been building further by adding some new UI elements. We’ll also look at some mobile/touchscreen UI and interactions.
其中一些示例似乎更适合按钮章节,但由于它们包括对图像组件的属性的访问,因此我将它们放在这里。
Some of these examples may seem better suited for the chapter on Buttons, but since they include access to the Image component’s properties, I placed them here.
笔记
Note
我们创建了两个场景,它们将加载到我们一直在构建的场景中:一个开始屏幕和一个介绍场景。由于我一直在复制我们的主要场景,以便于跟踪每个章节的进度,因此我们的介绍场景将无法导航到我们在本章和未来章节中所做的更新,除非我们不断更新介绍场景中对话框组件上的下一个场景变量,并在我们的构建设置中包含新场景。
We have created two scenes that load into the scene we’ve been building upon: a start screen and an intro scene. Since I’ve been duplicating our main scene to make progress from each chapter easy to track, our intro sScene will not navigate to the updates we make in this and future chapters unless we keep updating the Next Scene variable on our Dialogue Boxes component in the intro sScene and including the new scene in our Build Settings.
由于场景导航不再是这些示例的重点,因此我不会将此更新包括在步骤中。但是,它将包含在我在每个章节的完整 sc中包含的包中烯。
I will not be including this update in the steps since scene navigation is no longer a focus of these examples. However, it will be included in the packages I include in each chapter’s completed scenes.
让我们回到主场景。复制第 10 章示例场景以创建第 11 章示例场景。
Let’s get back to our main scene. Duplicate the Chapter10-Examples scene to create a Chapter11-Examples scene.
在本节中,我们将介绍如何创建两种类型的进度表,水平进度表和圆形进度表,如下面的屏幕截图所示:
In this section, we’ll cover how to create two types of progress meters, a horizontal one and a circular one, as shown in the following screenshot:
图 11.13:水平和垂直进度条示例
Figure 11.13: Example of horizontal and vertical progress bars
我们将连接圆形和水平的进度表,以便它们都显示同一变量的进度,并且我们可以同时看到它们的变化。
We’ll hook up the circular and horizontal progress meters so that they both display the progress of the same variable, and we can watch them both change at the same time.
圆形进度条不太适合我们一直在构建的主场景,我们将在本章之后隐藏它,但圆形进度条是常见的游戏元素,所以我认为在本章中包含一个如何使用它们的示例很重要之三
The circular progress meter doesn’t really fit in the main scene that we’ve been building, and we’ll hide it after this chapter, but circular progress bars are common game elements, so I thought it was important to include an example of how to do them in this chapter.
有几种不同的方法可以可以创建水平健康条,但最快捷、最简单的方法是根据百分比缩放单个轴。以这种方式设置水平健康条时,重要的是确保锚点设置在代表完全耗尽的健康条的位置。
There are a few different ways that a horizontal health bar can be created, but the quickest and easiest way is to scale a single axis based on percentage. When setting up a horizontal health bar in this way, it is important to ensure that the anchor is set at a position that represents a completely depleted bar.
记得在第 6 章中,我们将生命值条的锚点设置在左侧,因此我们已经正确设置了锚点。我们还在x方向上缩放了生命值条,以显示生命值条耗尽时的样子。
Remember that back in Chapter 6, we set the anchor of the health bar to the left, so we have already set the anchor correctly. We also scaled the health bar in the x direction to show what the bar would look like as it depleted.
图 11.14:生命值条的矩形变换组件
Figure 11.14: The Health Bar’s Rect Transform component
现在,我们需要做的就是将百分比与健康条的X 比例值联系起来。
Now, all we need to do is tie the percentage to the X Scale value of the health bar.
To tie the fill of the health bar to an actual value, complete the following steps:
公共单位健康; [SerializeField] uint 总健康值; [SerializeField]浮动百分比健康; [SerializeField]RectTransform健康栏;
health变量表示玩家当前的健康值,totalHealth变量表示玩家可以获得的总健康值。由于这些值为负数是没有意义的,因此它们被初始化为uint类型或正整数。我已将health变量设为公共变量,以便可以通过其他脚本访问并在 Inspector 中看到它。我将totalHealth设为私有SerializeField,以便它无法通过其他脚本访问,但仍可通过Inspector查看和分配。
percentHealth变量将根据health和totalHealth变量的商来计算。我将此值设为私有并序列化,这样做不是为了我们可以在 Inspector 中编辑它,而是为了我们可以在Inspector 中轻松看到其值的变化。
public uint health; [SerializeField] uint totalHealth; [SerializeField] float percentHealth; [SerializeField] RectTransform healthBar;
The health variable represents the current health of the player, and the totalHealth variable represents the total health the player can obtain. As it doesn’t make sense for these values to be negative, they have been initialized at the uint type or a positive integer. I have made the health variable public so that it can be accessed via other scripts and seen within the Inspector. I made totalHealth a private SerializeField so that it cannot be accessed via other scripts but still be seen and assigned via the Inspector.
The percentHealth variable will be calculated based on the quotient of the health and totalHealth variables. I made this value private and serialized, not so that we can edit it in the Inspector but so that we can easily see its value change in the Inspector.
The healthBar variable stores the RectTransform component of the Health Bar UI Image within our scene.
笔记
Note
由于RectTransform继承自Transform ,我们可以将healthBar声明为Transform,并且以下代码仍然有效。
Since RectTransform inherits from Transform, we could have declared healthBar as a Transform and the following code would still work.
图 11.15:进度表组件
Figure 11.15: The Progress Meters component
无效更新()
{
// 上限健康
如果 (健康 > 总健康)
{
健康=总健康;
}
// 计算健康百分比
百分比健康 = (浮点数)健康 / 总健康;
// 更新水平健康条
healthBar.localScale = new Vector2(percentHealth, 1f);
}使用uint类型声明health和totalHealth变量可以防止它们变为负数,但我们仍需要为health变量设置上限。它超出totalHealth变量是没有意义的。
虽然percentHealth是一个浮点变量,但在两个uint变量之间执行除法将产生uint类型,因此在整数除法的开头添加(float)可提供除法的浮点结果。
代码的最后一部分设置healthBar的localScale值。缩放 UI 对象时,必须使用localScale。这会在本地缩放对象,即相对于其父对象。
void Update()
{
// Cap health
if (health > totalHealth)
{
health = totalHealth;
}
// Calculate health percentage
percentHealth = (float)health / totalHealth;
// Update horizontal health bar
healthBar.localScale = new Vector2(percentHealth, 1f);
}Declaring our health and totalHealth variables with the uint type stopped them from becoming negative, but we still need to put an upper cap on our health variable. It doesn’t make sense for it to exceed the totalHealth variable.
While percentHealth is a float variable, performing a division between two uint variables will result in a uint type, so adding (float) at the beginning of the integer division provides a float result from the division.
The last part of the code sets the localScale value of the healthBar. When you scale a UI object, you have to use localScale. This scales the object locally, meaning relative to its parent object.
图 11.16:进度表组件对仪表的影响
Figure 11.16: The Progress Meters component’s effect on the meter
当这些箭头出现时,单击并拖动将根据鼠标位置操纵变量的值。执行此操作时,您会看到,随着健康值的减少,健康百分比值也会减少,并且场景中的健康栏大小会发生变化。您会注意到,您不能将健康值设置为低于0或高于500。
When these arrows appear, clicking and dragging will manipulate the values of the variable based on your mouse position. You’ll see, as you do this, that as the Health value decreases, the Percent Health value decreases, and the Health Bar in the scene changes size. You’ll note that you cannot set the value of Health below 0 or above 500.
如您所见,设置水平生命条并不困难。在生命值因事件而减少的游戏中复制此过程不需要很多步骤即可实现。只需确保正确设置生命条的锚点即可。此过程对于垂直生命条的工作方式类似 健康哈杆。
As you can see, setting up a horizontal health bar isn’t terribly difficult. Duplicating this process in a game where the health reduces by Events won’t require a lot of steps to achieve. Just ensure that you set the anchor of the health bar correctly. This process will work similarly for a vertical health bar.
水平健康条的设置工作并不多。制作圆形进度条的工作同样简单,只需两行代码即可完成。由于我们的场景中还没有圆形进度条,因此我们必须先进行一些设置。
Horizontal health bars didn’t take a lot of work to set up. The work to make a circular progress meter is just about as easy and can be completed with only two more lines of code. Since we don’t already have a circular progress bar in our scene, we will have to start with a bit of setup first.
To create a circular progress bar, complete the following steps:
图 11.17:进度表上的进度
如果粉色填充不完美位于蓝色支架内,您可能忘记点击其中一张图片上的“设置原始尺寸”按钮,或者进度表没有将其锚点预设设置为中间位置。
Figure 11.17: The progress on the Progress Meter
If the pink fill is not perfectly nestled inside the blue holder, you may have forgotten to hit the Set Native Size button on one of the Images or the Progress Meter does not have its anchor preset set to middle center.
图 11.18:重新定位圆形进度表
Figure 11.18: Repositioning the circular progress meter
图 11.19:调整圆形进度表上的填充量
Figure 11.19: Adjusting the fill amount on the circular progress meter
[SerializeField] 图像进度条;
[SerializeField] Image progressMeter;
// 圆形进度条 progressMeter.fillAmount = percentHealth;
// Circular progress meter progressMeter.fillAmount = percentHealth;
图 11.20:进度表组件的更新
Figure 11.20: The Progress Meter component’s updates
图 11.21:进度表结果
Figure 11.21: Result of the Progress Meter
正如您所见,制作圆形进度条并不比制作水平进度条困难!
As you can see, making a circular progress meter is really not more difficult than making a horizontal one!
笔记
Note
就像我们使用圆形进度条的填充量一样,我们也可以使用水平健康条的填充量。将图像类型属性设置为填充和水平,然后影响填充量值而不是比例,会产生类似的效果。
In the same way we used the fill amount for the circular progress bar, we could have used the fill amount for the horizontal health bar. Setting the Image Type property to Filled and Horizontal and then affecting the Fill Amount value rather than the scale would have had a similar effect.
因为这个圆形进度计不是原始 UI 计划的一部分,只是为了演示目的而放置在场景中,我将在未来的所有图形和屏幕中禁用它镜头。
Because this circular progress meter wasn’t part of the original UI plan and was only placed in the scene for demonstration purposes, I am going to disable it in all future figures and screenshots.
现在,让我们看一个例子,我们交换基于预定义状态的按钮精灵。这与我们在第 9 章中讨论的精灵交换过渡不同,因为它不会使用突出显示、按下、选中或禁用的状态。它包含在本章中,而不是按钮章中,因为它涉及影响图像组件,而不是按钮组件。
Now, let’s look at an example where we swap the sprite of a Button based on a pre-defined state. This is different than a sprite swap transition, which we discussed in Chapter 9, because it won’t use the states of highlighted, pressed, selected, or disabled. It’s included in this chapter rather than the Buttons chapter since it involves affecting the Image component, not the Button component.
在场景中,我们有一个暂停面板,当按下键盘上的P键时,它会弹出。在这个面板上,我们将放置两个静音按钮,一个用于音乐,一个用于声音,它们将在静音和非静音状态之间切换。面板将如以下屏幕截图所示:
In the scene, we have a Pause Panel that pops up when the P key is hit on the keyboard. On this Panel, we will place two mute Buttons, one for music and one for sound, which will toggle between muted and unmuted states. The Panel will appear as shown in the following screenshot:
图 11.22:带有新静音按钮的暂停面板
Figure 11.22: The Pause Panel with new mute Buttons
To add the music and sound Buttons shown in the preceding screenshot, complete the following steps:
图 11.23:muteUnmute.png 精灵
将此.png文件导入到项目的Assets/Sprites文件夹中。
Figure 11.23: The muteUnmute.png sprite
Import this .png file into your project’s Assets/Sprites folder.
图 11.24:muteUnmute.png 精灵切片
Figure 11.24: The muteUnmute.png sprite sliced
图 11.25:层次结构的当前视图
Figure 11.25: The current view of the Hierarchy
图 11.26:两个按钮的 Rect Transform
您的面板现在应该看起来就像本示例开头的面板一样:
图 11.27:暂停面板
Figure 11.26: The Rect Transform of the two Buttons
Your Panel should now look just like the one at the beginning of this example:
Figure 11.27: The Pause Panel
将MuteUnmute的代码替换为以下内容:
公共类 MuteUnmute : MonoBehaviour
{
[SerializeField] 按钮音乐按钮;
私人图像音乐图像;
[SerializeField] private Sprite[] musicSprites = new Sprite[2];
私人 bool musicOn = true;
[SerializeField] 按钮声音按钮;
私人图像声音图像;
[SerializeField] 私有 Sprite[] soundSprites = new Sprite[2];
私人 bool soundOn = true;
无效唤醒()
{
音乐图像 = 音乐按钮.GetComponent<图像>();
声音图像 = 声音按钮.GetComponent<图像>();
}
公共无效切换音乐()
{
音乐开启 = !音乐开启;
音乐图像.精灵 = 音乐精灵[Convert.ToInt32(musicOn)];
}
公共无效切换声音()
{
声音开启 = !声音开启;
声音图像.精灵 = 声音精灵[Convert.ToInt32(soundOn)];
}
}Replace the code of MuteUnmute with the following:
public class MuteUnmute : MonoBehaviour
{
[SerializeField] Button musicButton;
private Image musicImage;
[SerializeField] private Sprite[] musicSprites = new Sprite[2];
private bool musicOn = true;
[SerializeField] Button soundButton;
private Image soundImage;
[SerializeField] private Sprite[] soundSprites = new Sprite[2];
private bool soundOn = true;
void Awake()
{
musicImage = musicButton.GetComponent<Image>();
soundImage = soundButton.GetComponent<Image>();
}
public void ToggleMusic()
{
musicOn = !musicOn;
musicImage.sprite = musicSprites[Convert.ToInt32(musicOn)];
}
public void ToggleSound()
{
soundOn = !soundOn;
soundImage.sprite = soundSprites[Convert.ToInt32(soundOn)];
}
}如你所见,此代码包含两个主要函数:ToggleMusic()和ToggleSound()。这两个函数完全相同,只是根据musicOn和soundOn 布尔值交换指定按钮上的精灵。
As you can see, this code contains two main functions: ToggleMusic() and ToggleSound(). These two function identically by simply swapping the sprite on the specified Button based on the musicOn and soundOn Boolean values.
要交换精灵,脚本首先在Awake()函数中查找指定为musicButton和soundButton的两个按钮上的 Image 组件。这些按钮将在 Inspector 中分配。然后它将 Image 组件的精灵交换为正确的精灵来自精灵数组。静音和取消静音状态的精灵将在后续步骤中在 Inspector 中分配。
To swap the sprite, the script first finds the Image component on the two Buttons specified as musicButton and soundButton, within the Awake() function. These Buttons will be assigned in the Inspector. It then swaps the sprite of the Image component to the correct sprite from an array of sprites. The sprites for the mute and unmute states will be assigned in the Inspector in a future step.
笔记
Note
遗憾的是,本书没有介绍如何向 Unity 项目添加声音和音乐。此处提供的代码实际上并没有静音和取消静音;它只是交换精灵。您只需包含两个音频源:一个用于播放音乐,一个用于播放分别带有Music和Sound 标签的声音。
Sadly, this book does not cover adding sound and music to a Unity project. The code provided here doesn’t actually mute and unmute audio; it simply swaps sprites. You will simply need to include two audio sources: one for playing music and one for playing sounds that have the Music and Sound tags, respectively.
图 11.28:静音取消静音组件
Figure 11.28: The Mute Unmute component
图 11.29:更新后的静音取消静音组件
Figure 11.29: The updated Mute Unmute component
现在,玩游戏,按P调出暂停面板,你会看到按钮在两个不同的状态之间来回切换。公主。
Now, play the game, press P to bring up the Pause Panel, and you will see the Buttons toggle back and forth between their two different sprites.
现在我们已经了解了如何实现进度条和精灵交换按钮,接下来让我们看看如何实现一些不同的移动设备特定交互。
Now that we’ve looked at how to implement progress meters and sprite swap Buttons, let’s look at how to implement a few different mobile-specific interactions.
按住在手机游戏中使用频率很高。许多在 PC 或 Web 上使用右键的游戏在转换到手机平台时也使用按住。
Press-and-hold is utilized frequently in mobile games. Many games that use right-click on a PC or the web use press-and-hold when they are converted to the mobile platform.
演示如何实现按住功能,我们将创建一个按钮,该按钮有一个表示按住时间的不断增大的圆环。一旦经过指定的时间量,就会触发一个函数:
To demonstrate how to implement press-and-hold functionality, we will create a Button that has a growing ring that represents hold time. Once a specified amount of time has passed, a function will fire:
图 11.30:按住按钮示例
Figure 11.30: Press-and-hold Button example
在处理此示例时,请务必记住,即使代码引用了指针,此功能也并非只适用于鼠标。将手指放在触摸屏上的作用与指针向下的作用相同,而抬起手指的作用与指针向上的作用相同。
When working on this example, it is important to remember that even though the code is referencing a pointer, this functionality does not work exclusively with a mouse. Placing a finger on a touchscreen functions in the same way as a pointer down, and picking up the finger works the same as a pointer up.
要创建一个带有表示按住时间的不断增大的环的按钮,请完成以下步骤:
To create a Button with a growing ring that represents hold time, complete the following steps:
使用 UnityEngine.UI;
using UnityEngine.UI;
私有 bool buttonPressed = false; 私有浮点开始时间 = 0f; 私有浮点保持时间 = 0f; [SerializeField] 私有 float longHoldTime = 1f;
buttonPressed变量将在Pointer Down事件中设置为true,在Pointer Up事件中设置为 false。startTime 变量将在触发 Pointer Down 事件时设置为当前时间。holdTime变量将确定自startTime以来已经过了多少时间。longHoldTime变量是长按完成之前必须按住按钮的时间。它是序列化的,因此可以轻松自定义。
private bool buttonPressed = false; private float startTime = 0f; private float holdTime = 0f; [SerializeField] private float longHoldTime = 1f;
The buttonPressed variable will be set to true with the Pointer Down Event and false with the Pointer Up Event. The startTime variable will be set to the current time when the Pointer Down Event is triggered. The holdTime variable will determine how much time has passed since startTime. The longHoldTime variable is the amount of time the Button must be held down before the long press is complete. It is serialized so that it can be easily customized.
[SerializeField] 私有图像径向填充图像;
[SerializeField] private Image radialFillImage;
公共无效PressAndRelease(bool pressStatus)
{
按钮按下=按下状态;
如果(!按钮按下)
{
保持时间=0;
径向填充图像.填充量 = 0;
}
别的
{
开始时间 = 时间.时间;
}
}此函数从事件触发器接受一个布尔变量。然后它将buttonPressed的值设置为传递的值。
释放按钮时,将向函数传递false值。如果传递的值为false,则已过去的时间量holdTime将重置为0,并且将radiusFillImage图像重置为fillAmount值0。
当按下按钮时,startTime值将设置为当前时间。
public void PressAndRelease(bool pressStatus)
{
buttonPressed = pressStatus;
if (!buttonPressed)
{
holdTime = 0;
radialFillImage.fillAmount = 0;
}
else
{
startTime = Time.time;
}
}This function accepts a Boolean variable from the Event Trigger. It then sets the value of buttonPressed to the passed value.
When the Button is released, a value of false will be passed to the function. If the value passed is false, the amount of time that has passed, holdTime, is reset to 0, and the radialFillImage Image is reset to have a fillAmount value of 0.
When the Button is pressed, the startTime value will be set to the current time.
公共无效LongPressCompleted()
{
径向填充图像.填充量 = 0;
Debug.Log("长按后执行某些操作");
}此函数实际上不执行任何操作,只是重置填充图像并打印出Debug.Log。但是,您以后可以重复使用此代码,并用更有趣和更有意义的操作替换Debug.Log行。
public void LongPressCompleted()
{
radialFillImage.fillAmount = 0;
Debug.Log("Do something after long press");
}This function doesn’t really do anything but reset the filling Image and print out a Debug.Log. However, you can later reuse this code and replace the Debug.Log line with more interesting and meaningful actions.
无效更新()
{
如果(按钮按下)
{
保持时间 = 时间.时间 - 开始时间;
如果 (保持时间 >= 长保持时间)
{
按钮按下=false;
长按完成();
}
别的
{
径向填充图像.填充量 = 保持时间 / 长保持时间;
}
}
}如果buttonPressed值设置为true ,此代码会使holdTime的值向上跳动。请记住 - buttonPressed将通过指针向下事件设置为true ,通过指针向上事件设置为false。因此,只有当玩家按下按钮但尚未释放时,它才会为 true 。
一旦holdTime值达到longHoldTime指定的值,计时器将停止计时,因为buttonPressed将被重置为false。此外,还会调用LongPressCompleted()函数。如果尚未达到longHoldTime ,则图像的径向填充将更新以表示已过的总所需时间百分比。
void Update()
{
if (buttonPressed)
{
holdTime = Time.time - startTime;
if (holdTime >= longHoldTime)
{
buttonPressed = false;
LongPressCompleted();
}
else
{
radialFillImage.fillAmount = holdTime / longHoldTime;
}
}
}This code makes the value of holdTime tick upward if the buttonPressed value is set to true. Remember—buttonPressed will be set to true with a Pointer Down Event and false with a Pointer Up Event. So, it will only be true if the player has pressed the Button and not yet released it.
Once the holdTime value reaches the value specified by longHoldTime, the timer will stop ticking up, because buttonPressed will be reset to false. Additionally, the LongPressCompleted() function is called. If longHoldTime has not yet been reached, the Image’s radial fill will update to represent the percentage of total required time that has transpired.
图 11.31:事件触发器组件
Figure 11.31: The Event Trigger component
图 11.32:长按组件
Figure 11.32: The Long Press component
现在玩游戏将演示按住按钮时图像径向填充,并在控制台中长按后打印“执行某些操作”。如果在填充完成之前释放按钮,它将消失并在您再次开始单击时重置。
Playing the game now will demonstrate the Image radially filling when you hold the Button and printing Do something after long press in the console. If you release the Button before the fill has completed, it will go away and reset for when you start clicking again.
按住是一项非常常见的功能,虽然它不是 Unity 事件库中预安装的事件,但幸运的是,连接起来并不太难。我建议保留该脚本,以便您可以在未来。
Press-and-hold is a pretty common functionality, and while it isn’t a pre-installed Event in the Unity Event library, luckily it isn’t too difficult to hook up. I recommend holding on to that script so that you can reuse it in the future.
创建静态四向虚拟方向键
Creating a static four-directional virtual D-Pad
D-Pad 就是方向键上的四个按钮。要为移动游戏创建 D-Pad,您只需创建一个包含四个方向按钮的图形。
A D-Pad is simply four Buttons on a directional pad. To create a D-Pad for a mobile game, you just need to create a graphic that contains four Buttons on the directions.
本例中使用的艺术作品来自https://opengameart.org/content/onscreen-controls-8-styles。
The art used in this example was obtained from https://opengameart.org/content/onscreen-controls-8-styles.
要创建虚拟 D-Pad,请完成以下步骤:
To create a virtual D-Pad, complete the following steps:
图 11.33:方向键正确定位
Figure 11.33: The D-Pad positioned correctly
图 11.34:带有向上按钮的方向键
Figure 11.34: The D-Pad with an Up Button
图 11.35:方向键及其所有按钮
这四个按钮覆盖了方向键臂的整个区域。它们将充当方向的点击区域。
Figure 11.35: The D-Pad with all of its Buttons
These four Buttons cover the entire area of the arms of the directional pad. They will act as the hit area for the directions.
图 11.36:方向键的向上箭头
Figure 11.36: The Up Arrow of the D-Pad
图 11.37:方向键上的所有箭头
Figure 11.37: All Arrows on the D-Pad
Playing the game and selecting the four directional Buttons should result in the appropriate message being displayed on the console.
许多方向键实际上接受九个输入:四个方向键、四个对角线(角)和中心。如果您希望方向键既接受对角线输入,又接受中心单击,我建议使用网格布局组来均匀分布九个按钮。
Many D-Pads actually accept nine inputs: the four directs, the four diagonals (corners), and the center. If you want to accept diagonal inputs as well as a center-click for your D-Pad, I’d suggest using a grid layout group to evenly space your nine Buttons.
由于方向键通常允许按住,因此您可能希望将此示例中使用的过程与上一个示例中描述的类似操作相结合。您可以设置事件触发器来使用OnPointerDown和OnPointerUp事件,而不是使用On Click ()事件。然后,这些事件可以将布尔变量设置为true和false。例如,在右按钮上,您可以让OnPointerDown事件将名为moveRight 的变量设置为true,让OnPointerUp事件将moveRight设置为false。
Since D-Pads tend to allow press-and-hold, you may want to combine the process used in this example with actions similar to those described in the previous example. Instead of using the On Click () Event, you could set up an Event Trigger for using the OnPointerDown and OnPointerUp Events. These Events could then set a Boolean variable to true and false. For example, on the Right Button, you could have the OnPointerDown Event set a variable called moveRight to true and the OnPointerUp Event set moveRight to false.
在此示例中,我们将创建一个浮动的八向虚拟模拟摇杆。首先,我们将创建一个八向 D-Pad,模拟沿玩家拖动方向移动的控制杆:
In this example, we will create a floating eight-directional virtual analog stick. First, we will create an eight-directional D-Pad that simulates a control stick that moves in the direction the player drags:
图 11.38:浮动摇杆的位置
Figure 11.38: The positions of the floating analog stick
然后,我们将扩展八向 D-Pad,使其浮动,这意味着在玩家按下屏幕中的某个位置之前,它不会在场景中可见。然后,它将出现在玩家的拇指的位置,并根据玩家的拇指进行八个方向的移动拖拽。
Then, we will expand the eight-directional D-Pad so that it is floating, which means it will not be visible in the scene until the player presses somewhere in the screen. Then, it will appear where the player’s thumb is located and perform the eight-direction movement based on the player’s thumb dragging.
设置八向虚拟模拟摇杆
Setting up the eight-directional virtual analog stick
创建一个以八次为单位移动的模拟摇杆方向,如上图所示,完成以下步骤:
To create an analog stick that moves in eight directions, as shown in the previous figure, complete the following steps:
图 11.39:Stick 的 Rect Transform 组件
Figure 11.39: The Rect Transform component of the Stick
使用 UnityEngine.UI;
using UnityEngine.UI;
[SerializeField] 私有 RectTransform theStick; 私人向量2鼠标起始位置; 私人Vector2鼠标当前位置; [SerializeField] 私有 int dragPadding = 30;
前三个变量的含义非常明确。dragPadding变量用于确定玩家必须拖动操纵杆多远才能真正将其移动。
[SerializeField] private RectTransform theStick; private Vector2 mouseStartPosition; private Vector2 mouseCurrentPosition; [SerializeField] private int dragPadding = 30;
The first three variables should be pretty self-explanatory. The dragPadding variable will be used to determine how far the player has to drag the stick before it actually registers as being moved.
公共无效移动左()
{
Debug.Log("向左移动");
}
公共无效移动右()
{
Debug.Log("向右移动");
}
公共无效移动()
{
Debug.Log("向上移动");
}
公共无效移动向下()
{
Debug.Log("向下移动");
}public void MovingLeft()
{
Debug.Log("move left");
}
public void MovingRight()
{
Debug.Log("move right");
}
public void MovingUp()
{
Debug.Log("move up");
}
public void MovingDown()
{
Debug.Log("move down");
}公共无效StartDrag()
{
鼠标起始位置 = 输入.鼠标位置;
}请记住 - 使用触摸屏时,Input.mousePosition将提供触摸位置的值。
public void StartDrag()
{
mouseStartPosition = Input.mousePosition;
}Remember—when working with a touchscreen, Input.mousePosition will give the value of the touch position.
公共无效拖动()
{
浮动xPos;
浮动yPos;
鼠标当前位置 = 输入.鼠标位置;
如果 (mouseCurrentPosition.x < mouseStartPosition.x - dragPadding)
{
向左移动();
x位置=-10;
}
否则,如果(mouseCurrentPosition.x > mouseStartPosition.x + dragPadding)
{
向右移动();
x位置=10;
}
别的
{
x位置=0;
}
如果 (mouseCurrentPosition.y > mouseStartPosition.y + dragPadding)
{
向上移动();
y位置=10;
}
否则,如果(mouseCurrentPosition.y < mouseStartPosition.y - dragPadding)
{
向下移动();
y位置=-10;
}
别的
{
y位置=0;
}
theStick.anchoredPosition = new Vector2(xPos, yPos);
}public void Dragging()
{
float xPos;
float yPos;
mouseCurrentPosition = Input.mousePosition;
if (mouseCurrentPosition.x < mouseStartPosition.x - dragPadding)
{
MovingLeft();
xPos = -10;
}
else if (mouseCurrentPosition.x > mouseStartPosition.x + dragPadding)
{
MovingRight();
xPos = 10;
}
else
{
xPos = 0;
}
if (mouseCurrentPosition.y > mouseStartPosition.y + dragPadding)
{
MovingUp();
yPos = 10;
}
else if (mouseCurrentPosition.y < mouseStartPosition.y - dragPadding)
{
MovingDown();
yPos = -10;
}
else
{
yPos = 0;
}
theStick.anchoredPosition = new Vector2(xPos, yPos);
}公共无效StoppedDrag()
{
theStick.anchoredPosition = Vector2.zero;
}public void StoppedDrag()
{
theStick.anchoredPosition = Vector2.zero;
}图 11.40:事件触发器组件
Figure 11.40: The Event Trigger component
如果你现在玩游戏,你应该看到八向摇杆做出适当的反应。点击它,向任意方向拖动都会使操纵杆向方向移动阻力。
If you play the game now, you should see the eight-directional analog stick responding appropriately. Clicking on it and dragging in any direction will cause the stick to move in the direction of the drag.
如果您想要的只是一个八向模拟摇杆,那就太好了!但是,如果您想让模拟摇杆浮动(出现在玩家按下屏幕的位置,并在玩家抬起手指时消失),您就需要做更多的工作。
If all you want is an eight-directional analog stick, you’re good to go! But if you want the analog stick to float—appear where the players press on the screen and disappear when they lift their finger—you have to do a little bit more work.
To make the analog stick appear where the player clicks, complete the following steps:
图 11.41:显示点击区域和摇杆底座的层次结构
Figure 11.41: The Hierarchy showing Click Area and Stick Base
图 11.42:显示点击区域和摇杆底座的层次结构
Figure 11.42: The Hierarchy showing Click Area and Stick Base
[SerializeField] 私有 RectTransform theBase; [SerializeField] private bool stickAdded = false;
[SerializeField] private RectTransform theBase; [SerializeField] private bool stickAdded = false;
公共无效AddTheStick()
{
基准点.锚定位置 = 输入.鼠标位置;
theStick.anchoredPosition = Vector2.zero;
鼠标起始位置 = 输入.鼠标位置;
坚持添加 = 真;
}public void AddTheStick()
{
theBase.anchoredPosition = Input.mousePosition;
theStick.anchoredPosition = Vector2.zero;
mouseStartPosition = Input.mousePosition;
stickAdded = true;
}无效更新()
{
如果 (stickAdded == true)
{
拖拽();
如果(输入.GetMouseButtonUp(0))
{
// ToggleBaseCanvasGroup(false); // 此行被注释掉,因为提供的代码中未定义 ToggleBaseCanvasGroup
坚持添加 = 假;
停止拖动();
}
}
}void Update()
{
if (stickAdded == true)
{
Dragging();
if (Input.GetMouseButtonUp(0))
{
// ToggleBaseCanvasGroup(false); // This line is commented out as ToggleBaseCanvasGroup is not defined in the provided code
stickAdded = false;
StoppedDrag();
}
}
}图 11.43:浮动模拟摇杆组件
Figure 11.43: The Floating Analog Stick component
图 11.44:事件触发器组件
现在玩游戏时,模拟摇杆会出现在您点击的位置并通过拖动移动。
Figure 11.44: The Event Trigger component
Playing the game now will have the analog stick appear where you click and move around with your dragging.
私人CanvasGroup theBaseVisibility;
private CanvasGroup theBaseVisibility;
无效唤醒()
{
theBaseVisibility = theBase.GetComponent<CanvasGroup>();
}void Awake()
{
theBaseVisibility = theBase.GetComponent<CanvasGroup>();
}公共无效ToggleBaseCanvasGroup(bool可见)
{
theBaseVisibility.alpha = Convert.ToInt32(可见);
BaseVisibility.可交互 = 可见;
BaseVisibility.blocksRaycasts = 可见;
}public void ToggleBaseCanvasGroup(bool visible)
{
theBaseVisibility.alpha = Convert.ToInt32(visible);
theBaseVisibility.interactable = visible;
theBaseVisibility.blocksRaycasts = visible;
}切换BaseCanvasGroup(真);
ToggleBaseCanvasGroup(true);
切换BaseCanvasGroup(false);
ToggleBaseCanvasGroup(false);
现在,当玩家按下时,模拟摇杆就会出现,并沿着手指的方向移动,而当玩家抬起手指时,模拟摇杆就会消失。
Now, the analog stick will appear when the player presses down, move in the direction of their finger, and disappear when the player lifts their finger.
UI 图像是 Unity UI 系统的核心组件之一,操作它们对于创建视觉交互用户界面至关重要。本章总结了我们在前几章中学到的所有技能,让我们创建利用事件、按钮和图像的有趣界面。
UI Images are one of the core components of the Unity UI system and manipulating them is essential to creating visually interactive user interfaces. This chapter culminated all the skills we have learned in the preceding chapters by letting us create interesting interfaces that utilized Events, Buttons, and Images.
在下一章中,我们将研究如何创建遮罩和滚动视图,以便我们可以在Panel 容器中容纳更多的子对象。
In the next chapter, we’ll look at how to create masks and scroll views so that we can hold even more child objects within our Panel containers.
我们已经学习了如何使 UI 的所有组件同时显示在屏幕上,但通常您会有一些位于屏幕外或菜单外的 UI 元素,在您导航到它们或显示它们之前不可见。
We’ve learned how to make UI that has all of its components visible on the screen at once, but often you will have UI elements that are off-screen or off-menu and not visible until you navigate to them or reveal them.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
本章中展示的所有示例都可以在代码包中提供的 Unity 项目中找到。它们可以在标记为Chapter12 的场景中找到。
All the examples shown in this chapter can be found within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter12.
每个示例图像都有一个标题,注明场景中的示例编号。
Each example image has a caption stating the example number within the scene.
在场景中,每个示例都在其自己的画布上,并且某些画布已停用。要查看停用画布上的示例,只需在 Inspector 中选中画布名称旁边的复选框即可。每个画布还具有自己的事件系统。如果您一次激活多个画布,这将导致错误。
In the scene, each example is on its own Canvas, and some of the Canvases are deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector. Each Canvas is also given its own Event System. This will cause errors if you have more than one Canvas activated at a time.
您可以在此处找到本章节的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2012
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2012
口罩影响物体在其形状内的可见性。如果物体受到遮罩的影响,遮罩限制区域之外的任何部分都将不可见。遮罩的可见区域可以通过带有Mask组件的图像或带有Rect Mask 2D组件的 Rect Transform来确定。
Masks affect the visibility of objects within their shape. If an object is affected by a mask, any part of it outside the mask’s restricted area will be invisible. The visible area of a mask can either be determined by an Image with the Mask component or a Rect Transform with the Rect Mask 2D component.
在 UI 中,蒙版可用于滚动菜单,因此菜单区域之外的项目将不可见。它还用于剪切图像。例如,下图显示了一只猫的图像被圆形蒙版剪切掉:
With UI, masking can be used for scrolling menus, so items that exist outside of the menu’s area will not be visible. It is also used to cut out images. For example, the following image shows a cat’s image being cut out by a circular mask:
图 12.1:第 12 章场景中的圆形遮罩示例
Figure 12.1: Circular Mask Example in the Chapter12 scene
您可能会注意到,带面具的猫的边缘看起来不太好。为避免这种情况,请确保使用具有适当图像分辨率的精灵,并在精灵的导入设置中尝试不同的过滤模式。
You may note that the edges of the cat with the mask don’t look great. To avoid this, ensure that you use sprites with appropriate image resolutions and try different filtering modes on the Sprite’s Import Settings.
Mask组件可以添加到任何具有图像组件的 UI 对象。如果将其添加到没有图像组件的 UI 对象,它将不起作用,因为它需要图像来确定受限区域。
The Mask component can be added to any UI object with an Image component. If it is added to a UI object that doesn’t have an Image component, it won’t function since it needs an Image to determine the restricted area.
可以通过在 Inspector 中选择Add Component | UI | Mask将Mask组件添加到 UI对象:
The Mask component can be added to a UI object by selecting Add Component | UI | Mask within the Inspector:
图 12.2:Mask 组件
Figure 12.2: The Mask component
包含Mask组件的 UI 对象的任何子对象的可见性将被限制在其Image组件上的Source Image的不透明区域内。
Any children of the UI object containing the Mask component will then have their visibility restricted to only the area within the opaque area of the Source Image on its Image component.
下图中,名为Mask的对象是一个添加了Mask组件的 UI Image。如您所见,Panel 左侧的紫色三角形仅部分可见,而右侧的绿色三角形则完全可见。绿色三角形完全可见,因为它不是包含Mask组件的 UI Image 的子项:
In the following image, the object named Mask is a UI Image with a Mask component added to it. As you can see, the purple triangle on the left of the Panel is only partially visible, while the green triangle on the right is fully visible. The green triangle is fully visible because it is not a child of the UI Image that contains a Mask component:
图 12.3:第 12 章场景中的 Mask 组件示例
Figure 12.3: The Mask Component Example in the Chapter12 scene
您可以选择隐藏定义蒙版组件可见区域的源图像。如果取消选择“显示蒙版图形”,则父级的源图像将不可见。这一点很重要需要注意的是,更改源图像的不透明度不会影响Mask 组件的功能。
You have the option to hide the Source Image that defines the Mask component’s visibility area. If you deselect Show Mask Graphic, the parent’s Source Image will not be visible. It’s important to note that changing the opacity on the Source Image does not affect the Mask component’s functionality.
使用Mask组件允许您将可见区域限制为非矩形形状。但是,如果您想将可见区域限制为矩形形状,并且不想使用图像来限制可见区域,则可以使用Rect Mask 2D组件。
Using the Mask component allows you to restrict the visible area to a non-rectangular shape. However, if you want to restrict the visible area to a rectangular shape and don’t want to use an image to restrict the visible area, you can use a Rect Mask 2D component.
可以通过在Inspector中选择Add Component | UI | Rect Mask 2D将 Rect Mask 2D 组件添加到 UI Object 中,如下所示:
The Rect Mask 2D component can be added to a UI Object by selecting Add Component | UI | Rect Mask 2D within the Inspector, as shown in the following:
图 12.4:Rect Mask 2D 组件
Figure 12.4: A Rect Mask 2D component
请注意,该组件允许您调整填充 和柔软度。
Notice that the component allows you to adjust Padding and Softness.
当将Rect Mask 2D组件添加到 GameObject 时,其子对象的可见性将受到其 Rect Transform 形状的影响。父对象上不需要Image组件, Rect Mask 2D组件即可正常运行。
When a Rect Mask 2D component is added to a GameObject, the visibility of its children will be affected by the shape of its Rect Transform. An Image component is not required on the parent object for the Rect Mask 2D component to function properly.
在下文中图像,创建一个空的UI 游戏对象,并向其添加一个Rect Mask 2D组件。然后为其指定一个子 UI 图像。如您所见,三角形被 Rect Transform 区域遮盖:
In the following image, an Empty UI GameObject is created and a Rect Mask 2D component is added to it. It is then given a child UI Image. As you can see, the triangle is masked by the Rect Transform area:
图 12.5:第 12 章场景中的 Rect Mask 2D 组件示例
Figure 12.5: A Rect Mask 2D Component Example in the Chapter12 scene
如果您想将蒙版应用于矩形,我强烈建议您使用Rect Mask 2D组件而不是标准Mask组件,因为它的性能更佳。
If you want to apply a mask to a rectangular shape, I highly recommend that you use the Rect Mask 2D component rather than the standard Mask component, as it is more performant.
需要注意的是,正如名称Rect Mask 2D所暗示的那样,此遮罩仅适用于 2D 对象。您可以在https://docs.unity3d.com/Manual/script-RectMask2D.xhtml上阅读有关其限制的更多信息。
It’s important to note that, as implied by the name Rect Mask 2D, this mask will only work on 2D objects. You can read more about its limitations at https://docs.unity3d.com/Manual/script-RectMask2D.xhtml.
正如我之前所说,蒙版的一个常见用途是创建一个菜单,其中的对象溢出可见范围区域。创建这些类型的菜单需要滚动条和滚动视图,所以现在让我们看看这些组件。
As I stated earlier, one common use for a mask is to create a menu with objects that spill out of the visible area. Creating these types of menus requires scrollbars and scroll views, so let’s look at those components now.
UI滚动条对象允许用户沿路径拖动手柄。路径上手柄的位置会影响图像或对象在可用区域内的位置。
The UI Scrollbar object allows the user to drag a handle along a path. The position of the handle on the path affects the position of an image or object within a usable area.
如果您无法从上述描述中看出滚动条的含义,这里有一个更简单的上下文解释。它最常用于视频游戏中,菜单中包含大量信息,但可视区域小于所有信息所占的区域。
If you’re having trouble seeing what a scroll bar is from the preceding description, here’s an easier explanation with context. It is most commonly used in video games with menus that have a lot of information within a viewable area that is smaller than the area that all the information takes up.
要创建 UI Scrollbar,请选择+ | UI | Scrollbar。默认情况下,UI Scrollbar 有一个名为Sliding Area 的子项。Sliding Area还有一个名为Handle的子项。
To create a UI Scrollbar, select + | UI | Scrollbar. By default, a UI Scrollbar has a child named Sliding Area. The Sliding Area also has a child named Handle.
Sliding Area子对象是一个空的游戏对象。其目的是确保其子对象Handle的位置和对齐正确。Handle是一个 UI Image。它代表Scrollbar的可交互区域。
The Sliding Area child is an empty GameObject. Its purpose is to ensure that its child, the Handle, is correctly positioned and aligned. The Handle is a UI Image. It represents the interactable area of the Scrollbar.
如果要更改滚动条背景和Handle的外观,则需要分别更改滚动条父级和Handle子级上的图像组件的源图像 。
If you want to change the appearance of the Scrollbar’s background and Handle, you need to change the Source Images of the Image components on the Scrollbar parent and Handle child, respectively.
父母Scrollbar 对象有一个Scrollbar组件。它具有可交互 UI 对象共有的所有属性,以及Scrollbars 独有的一些属性:
The parent Scrollbar object has a Scrollbar component. It has all the properties common to the interactable UI objects, along with a few that are exclusive to Scrollbars:
Figure 12.6: The properties of the Scrollbar component
滚动条的值始终限制在0和1之间。滚动条用于移动需要比可视空间多出更多空间。因此,滚动条的位置应该很容易转换为百分比或0 到1之间的值。
A Scrollbar’s Value is always restricted to being between 0 and 1. Scrollbars are used to move objects that take up more room than the viewable space. Due to this, the Scrollbar’s position should easily translate to a percentage or a value between 0 and 1.
Handle Rect属性分配显示手柄图像的对象的 Rect Transform。默认情况下,Handle的 Rect Transform 被分配给此属性。您会注意到Handle GameObject上的 Rect Transform 组件具有由 Scrollbar 驱动的一些值消息,因为Handle的位置受 Scrollbar 的影响。Scrollbar 的Handle的位置与Scrollbar组件的Value属性相关联。
The Handle Rect property assigns the Rect Transform of the object that displays the handle’s image. By default, the Rect Transform of Handle is assigned to this property. You›ll note that the Rect Transform component on the Handle GameObject has the Some values driven by Scrollbar message, since the position of Handle is affected by the Scrollbar. The position of the Scrollbar’s Handle is tied to the Value property of the Scrollbar component.
图 12.7:Handle GameObject 上的 Rect Transform 组件
Figure 12.7: The Rect Transform component on the Handle GameObject
Direction属性允许您选择滚动条的方向。可用选项与 Slider 相同,并相应地进行平移:从左到右、从右到左、从下到上和从上到下。
The Direction property allows you to select the orientation of the Scrollbar. The available options are the same as with a Slider and translate accordingly: Left To Right, Right To Left, Bottom To Top, and Top To Bottom.
Size属性决定了 Scrollbar 的Handle占Scrollbar滑动区域的百分比。它可以是0到1之间的任意浮点值。我建议你选择一个相对于滚动对象大小的值,这样 Scrollbar 的移动会感觉更直观。这意味着可滚动区域越大,Scrollbar Handle就越小。
The Size property determines the percentage of the Scrollbar’s Sliding Area taken up by the Scrollbar’s Handle. This can be any float value from 0 to 1. I recommend that you choose a value relative to the size of the objects being scrolled so that the Scrollbar’s movement feels more intuitive. This means that the larger the scrollable area is, the smaller the Scrollbar Handle becomes.
如果您希望滚动条具有交错、离散的步进,并且不希望它具有连续受控的移动,则可以使用步进数属性。如果您希望滚动条将滚动对象移动到特定位置,则可以使用该属性。
The Number of Of Steps property is used if you want the Scrollbar to have staggered, discrete steps and don’t want it to have continuously controlled movement. This is used if you want your scrollbar to move the scrolling objects to specific locations.
通常,您会看到一个由点表示的滚动区域(例如,在 iOS 设备上,用于标识您正在查看哪个主屏幕的点)。这可以通过使用大于0的Number Of Steps属性来实现。将此值设置为0将允许连续控制移动而不是交错的离散步骤。
Often, you will see a scroll area represented by dots (like the dots that identify which home screen you are viewing on an iOS device). This can be achieved using a Number Of Steps property greater than 0. Setting this value to 0 will allow for continuously controlled movement rather than staggered discrete steps.
Examples of creating continuous and discrete Scrollbars are provided in the Examples section at the end of this chapter.
Scrollbar组件的默认事件是On Value Changed事件,如 Scrollbar 组件的On Value Changed (Single)部分所示。每当 Scrollbar 的Handle移动时,都会触发此事件。它可以接受浮点参数。
The Scrollbar component’s default event is the On Value Changed event, as seen in the On Value Changed (Single) section of the Scrollbar component. This event will trigger whenever the Scrollbar’s Handle is moved. It can accept a float argument.
当公共函数具有浮点参数时,它将在函数的“ On Value Changed (Single)”事件的下拉列表中出现两次:一次在“Static Parameters”列表中,另一次在“Dynamic float”列表中,如下面的屏幕截图所示:
When a public function has a float parameter, it will appear twice within the function’s dropdown list of On Value Changed (Single) events: once within a Static Parameters list and again within the Dynamic float list, as shown in the following screenshot:
图 12.8:静态参数和动态浮动方法
Figure 12.8: Static Parameters and Dynamic float methods
如果函数是从“静态参数”列表中选择后,将出现一个框,允许您在事件中输入浮点数作为参数。然后,事件将仅发送该框内的值。在以下屏幕截图中显示的示例中,将发送到ScrollbarWithParameter()函数的唯一值将是0。
If the function is selected from the Static Parameters list, a box will appear that allows you to enter a float as an argument within the event. The event will then only send the value within that box. In the example shown in the following screenshot, the only value that will ever be sent to the ScrollbarWithParameter() function will be 0.
Figure 12.9: Example of a Static Parameters method in the Inspector
如果您想要将滚动条的值作为参数发送给具有参数的函数,那么必须从动态浮点列表中选择该函数。
If you want the Scrollbar’s value to be sent as an argument to a function that has a parameter, you must select the function from the Dynamic float list.
以下函数和图像表示第 12 章场景中的滚动条示例,它触发调用带参数和不带参数的函数的事件:
The following functions and image represent the Scrollbar example found in the Chapter12 scene that triggers events that call functions with and without parameters:
公共无效ScrollbarWithoutParameter(){
Debug.Log("已改变");
}
公共无效ScrollbarWithParameter(浮点值){
调试.日志(值);
}public void ScrollbarWithoutParameter(){
Debug.Log("changed");
}
public void ScrollbarWithParameter(float value){
Debug.Log(value);
} 在下文中屏幕截图,第三个选项显示从动态浮点列表中选择的函数,并将Value属性的值作为参数发送给该函数:
In the following screenshot, the third option shows the function chosen from the Dynamic float list and will send the value of the Value property as an argument to the function:
图 12.10:第 12 章场景中的滚动条事件示例
Figure 12.10: Events on Scrollbar Example in the Chapter12 scene
现在我们已经回顾了如何在 Unity 中实现 UI 滚动条,我们来看看 UI滚动视图。
Now that we’ve reviewed how to implement a UI Scrollbar in Unity, let’s look at UI Scroll Views.
UI滚动视图对象创建一个可滚动区域以及两个子 UI 滚动条。可以使用滚动条、拖动滚动视图内的区域或使用鼠标上的滚轮来滚动可滚动区域。
The UI Scroll View object creates a scrollable area along with two child UI Scrollbars. The scrollable area can be scrolled using the scrollbars, by dragging the area within the Scroll View, or using the scroll wheel on the mouse.
要创建 UI 滚动视图,请选择+ | UI |滚动视图。默认情况下,UI 滚动视图有三个子项:Viewport、Scrollbar Horizontal和Scrollbar Vertical。Viewport对象还有一个名为Content 的子项。Scrollbar Horizontal和Scrollbar Vertical子项具有与默认 UI 滚动条相同的父子关系,如上一节所述。
To create a UI Scroll View, select + | UI | Scroll View. By default, a UI Scroll View has three children: Viewport, Scrollbar Horizontal, and Scrollbar Vertical. The Viewport object also has a child named Content. The Scrollbar Horizontal and Scrollbar Vertical children have the same parent/child relationship as default UI Scrollbars, as discussed in the previous section.
尽管 UI Scroll View默认带有两个Scrollbar子项,但您不必在Scroll View中使用这两个 Scrollbar 。事实上,您根本就不必使用 Scrollbar!有关更多详细信息,请参阅Scroll Rect 组件部分。
Even though the UI Scroll View comes with two Scrollbar children by default, you don’t have to use both Scrollbars with your Scroll View. In fact, you don’t have to use Scrollbars at all! Refer to the Scroll Rect component section for further details.
Viewport子对象是带有Mask组件的UI Image 。默认情况下, Viewport的Mask组件的Show Mask Graphic属性已关闭。从以下屏幕截图中的 Rect Transform 中可以看到,Viewport将遮罩应用于Scroll View中的某个区域:
The Viewport child object is a UI Image with a Mask component. The Mask component of Viewport has the Show Mask Graphic property turned off, by default. As you can see from the Rect Transform in the following screenshot, the Viewport applies a mask to an area within the Scroll View:
图 12.11:UI 滚动视图视口
Figure 12.11: A UI Scroll View Viewport
这将使滚动视图中的项目只能在定义的区域(滚动条之间)内查看。您无法更改视口的大多数Rect Transform属性,因为此区域的设置取决于您在其Scroll Rect组件中设置滚动视图的方式(有关更多详细信息,请参阅实现 UI 滚动条部分)。
This will make the items within the Scroll View only viewable within the defined area (between the Scrollbars). You cannot change most of the Rect Transform properties of the Viewport, because this area is set based on how you have the settings of your Scroll View set in its Scroll Rect component (refer to the Implementing UI Scrollbar section for more details).
Viewport的子项是一个名为Content 的空Rect Transform。它将充当您希望放置在Scroll View内的所有项目的持有者。您可以将Content视为将在Scroll View内移动的东西。从下图中可以看出, Content的 Rect Transform大于Viewport定义的可视区域,因为Scroll View的目标是让项目超出可视区域。
The child of Viewport is an empty Rect Transform named Content. This will act as the holder of all the items you wish to place within Scroll View. You can think of the Content as the things that will be moving around within the Scroll View. As you can see from the following image, the Rect Transform of Content is larger than the viewable area defined by Viewport, since the objective of a Scroll View is to have items outside of the viewable area.
图 12.12:UI 滚动视图内容
Figure 12.12: A UI Scroll View Content
要添加项目在Scroll View 中,您只需将子对象添加到Content对象即可。由于Content是Viewport的子对象,因此其任何子对象也将受到Viewport上的Mask组件的影响。
To add items to the Scroll View, you simply add children to the Content object. Since Content is a child of Viewport, any of its children will also be affected by the Mask component on the Viewport.
以下示例显示了将四幅图像添加为Content的子项。Content还被赋予了Vertical Layout Group组件:
The following example shows four images added as children of Content. Content has also been given a Vertical Layout Group component:
图 12.13:第 13 章场景中的滚动视图示例
Figure 12.13: A Scroll View example in the Chapter13 scene
在前面图像中,您可以看到,当从Viewport禁用Mask组件时,所有项目都可见。设置滚动视图时,我强烈建议您在Viewport上禁用Mask ,以便您可以看到所放置项目的总体布局,并在布置完所有项目后重新启用它。
In the preceding image, you can see that when the Mask component is disabled from the Viewport, all items are visible. When you are setting up your Scroll View, I highly recommend that you disable the Mask on the Viewport so that you can see the general layout of the items you are placing and re-enable it when you are done laying out all the items.
您还应该调整Content的 Rect Transform 区域以包含其所有子项,或者使用Content Size Fitter组件,以便更轻松地预测 Scroll View 的行为。如果 Rect Transform Content的区域未完全包含所有项,则滚动Scroll View时可能不会显示其可视区域之外的项。
You should also adjust the Rect Transform area of Content to enclose all of its child items or use a Content Size Fitter component so that you can more easily predict the behavior of Scroll View. If the area of the Rect Transform Content does not fully encompass all the items, scrolling the Scroll View may not show the items outside of its viewable area.
滚动视图包含一个图像组件。如果要更改滚动视图的背景(封装所有内容的灰色矩形),请更改滚动视图上图像组件的源图像。您可以通过调整滚动条水平和滚动条垂直子项的外观来更改滚动条的外观,如实现 UI滚动条部分中所述。
Scroll View contains an Image component. If you want to change the background of the Scroll View (the gray rectangle that encapsulates everything), change the Source Image of the Image component on the Scroll View. You can change the appearance of the Scrollbars by adjusting the appearance of the Scrollbar Horizontal and Scrollbar Vertical children, as described in the Implementing UI Scrollbar section.
现在我们了解 Scroll Rect 的用途和设置后,我们来看一下 Scroll Rect 组件的各个属性nent。
Now that we understand the intent and setup of a Scroll Rect, let’s look at the individual properties of the Scroll Rect component.
行为确定滚动视图的通过 Scroll View 父对象上的 Scroll Rect 组件:
The behavior of the Scroll View is determined by the Scroll Rect component on the Scroll View parent object:
图 12.14:Scroll Rect 组件
Figure 12.14: The Scroll Rect component
请注意滚动Rect 组件不具备本章(以及之前章节)中所有其他 UI 组件所具有的Interactable、Transition或Navigation属性!
Note that the Scroll Rect component doesn’t have the Interactable, Transition, or Navigation properties that all the other UI components in this chapter (and previous chapters) have!
我将稍微不按顺序讨论这些属性,以便于讨论。
I’ll discuss the properties slightly out of order to make them easier to discuss.
Content属性被分配了将要滚动的 UI 元素的 Rect Transform。使用 UI Scroll View 对象时,默认情况下将其设置为Content的 Rect Transform 组件。Viewport 属性被分配了 Rect Transform,它是分配给Content属性的项目的父级。使用 UI Scroll View 对象时,默认情况下将其设置为Viewport的 Rect Transform 组件。
The Content property is assigned the Rect Transform of the UI element that will scroll. When using the UI Scroll View object, this is set to the Rect Transform component of Content by default. The Viewport property is assigned the Rect Transform that is the parent of the item assigned to the Content property. When using the UI Scroll View object, this is set to the Rect Transform component of Viewport by default.
如果你正在创建一个可滚动区域而不使用 UI Scroll View,则必须分配Viewport和Content属性,并且分配给Viewport的 Rect Transform 必须是矩形分配给Content 的变换,以使Scroll Rect组件正常运行财产。
If you are creating a scrollable area without using the UI Scroll View, the Viewport and Content properties must be assigned and the Rect Transform assigned to Viewport must be a parent of the Rect Transform assigned to Content, for the Scroll Rect component to function property.
有三个与滚动视图中的内容如何移动相关的属性。水平和垂直属性分别启用和禁用水平和垂直方向的滚动。默认情况下,它们都是启用的。移动类型属性决定了滚动视图如何在其边界上移动。
There are three properties related to how the Content in the Scroll View will move. The Horizontal and Vertical properties enable and disable scrolling in the horizontal and vertical directions, respectively. By default, they are both enabled. The Movement Type property determines how the Scroll View moves at its boundaries.
有三种移动类型选项:不受限制、弹性和夹紧。这些移动类型仅影响可滚动区域对可滚动区域拖动和鼠标滚轮的反应方式。使用滚动条时,您不会注意到这三种移动类型之间的差异。当使用滚动条移动内容时,所有移动类型的行为都将与夹紧移动类型完全相同。
There are three Movement Type options: Unrestricted, Elastic, and Clamped. These Movement Types only affect the way the scrollable area reacts to being dragged by the scrollable area and to the scroll wheel on the mouse. You will not note a difference between these three Movement Types when you are using the Scrollbars. All Movement Types will behave exactly like the Clamped Movement Type when the Scrollbars are used to move the Content.
当选择“无限制”作为移动类型时,玩家可以无限拖动可滚动区域,而不受内容的矩形变换的限制。弹性和限制移动类型将在到达内容的矩形变换的边缘后停止移动内容。但是,当选择“无限制”作为移动类型时,玩家可以继续拖动或使用滚轮。如果视口上有遮罩,则可能导致玩家将所有内容拖到可视空间之外。但是,如果玩家使用滚动条移动内容,则它们将被限制在内容的范围内。
When Unrestricted is selected as the Movement Type the player can drag the scrollable area endlessly without restriction to the Rect Transform of Content. The Elastic and Clamped Movement Types will stop moving the Content once the edges of Rect Transform of Content have been reached. However, when Unrestricted is selected as a Movement Type, the player can continue to drag or use the scroll wheel. If there is a mask on the Viewport, this can result in the player dragging all content outside of the viewable space. If the player moves the content with the scrollbars, however, they will be restricted to the bounds of Content.
当选择“弹性”作为“移动类型”时,如果将内容拖过其边界,则一旦玩家停止拖动,内容就会弹回原位。如果使用滚轮,它也会弹回。选择“弹性”后,子属性“弹性”将变为可访问。“弹性”属性决定了弹回的强度。
When Elastic is selected as the Movement Type, if the Content is dragged past its boundary, it will bounce into place once the player stops dragging. This will also bounce if the scroll wheel is used. When Elastic is selected, the subproperty Elasticity becomes accessible. The Elasticity property determines the intensity of the bounce.
当选择“夹紧”作为移动类型时,内容将无法拖过其边界,也不会反弹会发生。
When Clamped is selected as the Movement Type, the Content will not be draggable past its boundary and no bounce will occur.
选择“惯性”属性将使内容在玩家停止拖动后继续移动。惯性仅在滚动区域被拖动时才会显现,并且不会影响由滚动条或鼠标滚轮初始化的内容移动。选择“惯性”后, “减速率”子属性将变为可访问。“减速率”属性决定了玩家停止拖动后内容何时停止移动。减速率为0时,内容将在玩家停止拖动的瞬间停止移动,减速率为1 时则永远不会停止移动。默认情况下,“减速率”设置为0.135。
Selecting the Inertia property will make the Content continue to move after the player has stopped dragging. Inertia is only apparent when the scroll area is dragged and doesn’t affect Content movement initialized by the Scrollbars or mouse scroll wheel. When Inertia is selected, the Deceleration Rate subproperty becomes accessible. The Deceleration Rate property determines when the Content will stop moving after the player has ceased dragging. A Deceleration Rate of 0 will stop the Content the instant the player stops dragging, and a Deceleration Rate of 1 will never stop. By default, Deceleration Rate is set to 0.135.
滚动灵敏度属性决定了滚轮每次转动时内容移动的距离。数字越高,内容每次转动移动的距离越远,从而使其似乎移动得更快。
The Scroll Sensitivity property determines how far the Content will move with each turn of the scroll wheel. The higher the number, the further the content will move with a turn, making it appear to move more quickly.
如果要禁用滚动视图的鼠标滚轮,请设置Scroll Sensi活动度 为0。
If you want to disable the use of the mouse scroll wheel for the Scroll View, set Scroll Sensitivity to 0.
您可以设置属性用于指定水平和垂直滚动条分别作出反应的方式。水平滚动条和垂直滚动条属性指定您希望分别使用的 UI 滚动条的滚动条组件。默认情况下,它们分别被指定为水平滚动条子项和垂直滚动条子项 。
You can set properties for the way your horizontal and vertical scrollbars react separately. The Horizontal Scrollbar and Vertical Scrollbar properties assign the Scrollbar components of the UI Scrollbars you wish to use for each. By default, these are assigned the Scrollbar Horizontal child and Scrollbar Vertical child, respectively.
如果您只想在滚动视图区域使用拖动并且不想要滚动条,您可以简单地将水平滚动条和垂直滚动条属性设置为无,或者从场景中删除水平滚动条和垂直滚动条对象。
If you only want to use drag on the Scroll View area and don’t want scrollbars, you can simply set the Horizontal Scrollbar and Vertical Scrollbar properties to None or delete the Scrollbar Horizontal and Scrollbar Vertical objects from the scene.
在每个滚动条分配下,您可以设置相应滚动条的可见性和间距属性。
Under each Scrollbar assignment, you can set the Visibility and Spacing properties of the respective Scrollbar.
Visibility属性有三个选项:Permanent、Auto Hide和Auto Hide And Expand Viewport。当Visibility属性选择Permanent时,如果允许其相应的移动,则相应的滚动条将保持可见,即使不需要它。例如,如下面的屏幕截图所示,如果允许水平移动,并且水平滚动条的Visibility设置为Permanent,则相应的滚动条将可见,即使不需要它(无法实现水平移动):
The Visibility property has three options: Permanent, Auto Hide, and Auto Hide And Expand Viewport. When Permanent is selected for the Visibility property, the respective Scrollbar will remain visible, even if it is not needed, if its corresponding movement is allowed. For example, as shown in the following screenshot, if Horizontal movement is allowed, and the Horizontal Scrollbar’s Visibility is set to Permanent, the respective scrollbar will be visible, even though it is not necessary (no horizontal movement can be achieved):
图 12.15:调整滚动矩形中的滚动条
Figure 12.15: Adjusting the Scrollbars in the Scroll Rect
然而,引用在同一张图片中,你可以看到,如果禁用了水平移动,则将水平滚动条的可见性设置为永久可将其从滚动视图中完全移除。它在层次结构中也被禁用。
However, referencing the same image, you can see that if Horizontal movement is deactivated, setting the Horizontal Scrollbar’s Visibility to Permanent removes it from the Scroll View entirely. It is also deactivated in the Hierarchy.
当为可见性属性选择了自动隐藏时,如果不需要相应的滚动条(意味着不需要在该方向上移动)或者禁用了相应的轴移动,则在游戏运行时,相应的滚动条将变得不可见并在层次结构中停用。
When Auto Hide is selected for the Visibility property, the respective Scrollbar will become invisible and deactivate in the Hierarchy when the game is played if it is not needed (meaning that there is no movement in that direction required) or if the respective axis movement is disabled.
Auto Hide And Expand Viewport属性的工作方式与Auto Hide相同,但它还会扩展分配给Viewport 的Rect Transform 的区域。如果Viewport对象具有Mask组件,这将导致 mask 的区域扩展以覆盖最初由Scrollbar占用的区域。
The Auto Hide And Expand Viewport property works in the same way as Auto Hide, but it will also expand the area of the Rect Transform assigned to Viewport. If the Viewport object has a Mask component, this will cause the mask’s area to expand to cover the area that was initially being taken up by the Scrollbar.
Spacing属性决定了Viewport的 Rect Transform 和Scrollbar的 Rect Transform之间的间距。默认情况下,此值设置为-3,这意味着两个 Rect Transform 略有重叠。如果要更改 Viewport 的位置,则必须使用此属性,因为与Viewport的位置相关的属性在其 Rect Transf中被禁用orm 组件。
The Spacing property determines the space between the Viewport’s Rect Transform and the Scrollbar’s Rect Transform. By default, this value is set to -3, which means the two Rect Transforms overlap slightly. If you want to change the position of the Viewport, you have to do so with this property, since the properties related to the Viewport’s position are disabled in its Rect Transform component.
滚动矩形组件的默认事件是On Value Changed事件,如Scroll Rect 组件的On Value Changed (Vector2)部分所示。每当通过拖动、使用鼠标滚轮滚动或使用滚动条之一滚动移动滚动视图的内容区域时,都会触发此事件。它接受Vector2位置作为参数,并且与本章中讨论的其他事件一样,您可以选择不传递参数、传递静态参数或传递动态参数。
The Scroll Rect component’s default event is the On Value Changed event, as seen in the On Value Changed (Vector2) section of the Scroll Rect component. This event will trigger whenever the Content area of the Scroll View is moved by dragging, scrolling with the mouse scroll wheel, or scrolling with one of the Scrollbars. It accepts a Vector2 position as an argument and, as with the other events discussed in this chapter, you can choose to pass no argument, a static argument, or a dynamic argument.
如果要将Content的Vector2位置发送给函数,则可以使用Dynamic Vector2列表中的Vector2参数将其发送给函数。发送的位置本质上是一个百分比,起始位置在相应坐标中的值为1.0 ,最后一个位置在相应坐标中的值为0.0 。
If you want to send the Vector2 position of Content to a function, you’d send it to a function with a Vector2 parameter from the Dynamic Vector2 list. The position sent is essentially a percentage, with the starting position having a value of 1.0 in the corresponding coordinate and the last position having a value of 0.0 in the corresponding coordinate.
让我们考虑以下函数:
Let’s consider the following function:
公共无效ScrollViewWithParameter(Vector2值)
{
调试.日志(值);
}public void ScrollViewWithParameter(Vector2 value)
{
Debug.Log(value);
} 如果从动态 Vector2列表中选择了上一个函数,则下图显示了控制台中打印的Vector2值:
If the previous function is selected from the Dynamic Vector2 list, the following image shows the Vector2 values printed in the Console:
图 12.16:基于位置的滚动矩形的值
Figure 12.16: Values of the Scroll Rect based on position
现在我们已经了解了各种 UI 元素后,我们来看一些imp的示例警告他们。
Now that we’ve looked at the various UI elements, let’s look at some examples of implementing them.
我之前提到过在本章中,遮罩和滚动视图的常见用法是包含多个项目的菜单。因此,在本章中,我们将仅创建一个示例,即来自现有菜单的滚动视图,通过模仿滚动的布局查看UI 对象。
I mentioned earlier in this chapter that a common usage of masks and scroll views is a menu with multiple items in it. So, for this chapter, we’ll only create one example, a scroll view from a pre-existing menu, by mimicking the layout of the Scroll View UI object.
帮助组织项目中,复制上一章中创建的Chapter11-Examples场景;将其重命名为Chapter12-Examples。打开Chapter12-Examples并在该场景中完成以下示例。
To help organize the project, duplicate the Chapter11-Examples scene that you created in the last chapter; rename it Chapter12-Examples. Open Chapter12-Examples and complete the following example within that scene.
我希望能够向我的库存面板添加更多物品,并允许玩家滚动浏览这些物品。我可以创建一个新的 UI 滚动视图项目并将其更新为看起来像我当前的库存面板,但这样做会比它的价值更麻烦。因此,我将把当前的库存面板转换为可滚动视图。完成后,它将看起来像下图:
I want to be able to add more items to my Inventory Panel and allow the player to scroll through the items. I could create a new UI Scroll View item and update it to look like my current Inventory Panel, but that would be more hassle than it’s worth. So, instead, I will convert the current Inventory Panel to a scrollable view. After it is complete, it will look like the following figure:
图 12.17:我们将要构建的可滚动菜单
Figure 12.17: The scrollable menu we will build
可以通过拖动食物旁边的区域来调整此面板的视图。我本来可以添加垂直滚动条来控制移动,但我想向您展示在没有滚动条的情况下制作可拖动区域是多么简单。而且没有滚动条看起来会更好看一些。
This Panel can have its view adjusted by dragging the area beside the food items. I could have added a vertical scrollbar to control the movement, but I wanted to show you how simple it is to make a draggable area without one. It also just looks a little nicer without a scrollbar.
要使库存面板可滚动,请完成以下步骤:
To make the Inventory Panel scrollable, complete the following steps:
图 12.18:步骤 2 后的结果
Figure 12.18: The result after Step 2
图 12.19:用于创建可滚动菜单的层次结构布局
让我们从将容纳滚动矩形组件的对象。选择库存面板,右键单击,然后选择创建空对象。这将创建一个空对象,它是我们的库存面板的子对象。
Figure 12.19: The Hierarchy layout to create a scrollable menu
Let’s start with the object that will hold the Scroll Rect component. Select Inventory Panel, right-click, and select Create Empty. This will create an empty object that is a child of our Inventory Panel.
图 12.20:Scroll Rect 对象的 Rect Transform
Figure 12.20: The Rect Transform of the Scroll Rect object
图 12.21:Scroll Rect 对象的 Rect Transform
Figure 12.21: The Rect Transform of the Scroll Rect object
图 12.22:步骤 11 的结果
Figure 12.22: The results of Step 11
图 12.23:步骤 12 的结果
Figure 12.23: The results of Step 12
图 12.24:如何定位内容
Figure 12.24: How to position the Content
图 12.25:分配内容和视口
Figure 12.25: Assigning the Content and Viewport
如果你玩游戏,库存面板现在应该有当你拖动它们时会滚动的物品。请记住,你可以通过按I键来调出库存面板键盘上的。由于我们在各个项目上具有拖放功能,因此要滚动,我们必须拖动没有上面的食物。
If you play the game, the Inventory Panel should now have items that scroll when you drag beside them. Remember that you bring up the Inventory Panel by pressing the I key on your keyboard. As we have drag and drop functionality on our individual items, to scroll, we have to drag the area of Content that does not have a food item on it.
本章介绍了如何隐藏 UI 元素的可见性以及如何使用滚动视图创建容器,这些容器可容纳比屏幕一次可容纳的更多的项目。但是,还有相当多的可交互 UI 元素。在下一章中,我们将介绍 Unity UI 系统提供的其余交互式 UI 元素。
This chapter covered how to hide the visibility of UI elements as well as use Scroll Views to create containers that hold more items than the screen can hold at one time. There are still quite a few more interactable UI elements, however. In the next chapter, we will look at the remaining interactive UI elements provided by the Unity UI system.
最流行的可交互 UI 组件是按钮。但是,除了按钮之外,还有多种类型的可交互 UI 元素。如果您想想最近填写的在线表单,您可能已经与按钮、文本字段以及单选按钮或复选框进行了交互。虽然从技术上讲,所有这些可交互项目都可以使用 UI 按钮、UI 文本和一些自定义代码来开发,但您不必自己构建它们!Unity 在 uGUI 系统中包含了多个常用的可交互 UI 项目,既可以作为您可以编辑的游戏对象,也可以作为您可以添加到现有游戏对象的组件。
The most popular interactable UI components are Buttons. However, there are multiple types of interactable UI elements other than buttons. If you think of an online form you’ve filled out recently, you’ve probably interacted with buttons, text fields, and possibly a radio button or checkbox. While technically all of these interactable items can be developed with UI Buttons, UI Text, and some custom code, you don’t have to build them yourself! Unity has included, within the uGUI system, multiple commonly used interactable UI items both as GameObjects that you can edit and as components that you can add to pre-existing GameObjects.
本章将回顾 uGUI 系统附带的所有其他预构建 UI 项目。在阅读了按钮和文本章节后,您将熟悉这些对象的大部分属性,但每个可交互项目都有一些专属于该 UI 项目类型的属性,因此我们将重点介绍这些属性。
This chapter will review all the other pre-built UI items that come with the uGUI system. After having reviewed the chapters on buttons and text, most of these objects’ properties will be familiar to you, but each interactable item has a few properties exclusive to that UI item type, so we’ll focus on those properties.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
笔记
Note
本节中展示的所有示例都可以在代码包中提供的 Unity 项目中找到。它们可以在标记为Chapter13 的场景中找到。
All the examples shown in this section can be found within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter13.
每个示例图像都有一个标题,注明场景中的示例编号。
Each example image has a caption stating the example number within the scene.
在场景中,每个示例都在其自己的画布上,并且某些画布已停用。要查看停用画布上的示例,只需在 Inspector 中选中画布名称旁边的复选框即可。每个画布还具有自己的事件系统。如果您一次激活多个画布,这将导致错误。
In the scene, each example is on its own Canvas, and some of the Canvases are deactivated. To view an example on a deactivated Canvas, simply select the checkbox next to the Canvas’ name in the Inspector. Each Canvas is also given its own Event System. This will cause errors if you have more than one Canvas activated at a time.
您可以在此处找到本章的代码: https: //github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2013
You can find the code for this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2013
UI Toggle对象是一个带有标签的可交互复选框。要创建 UI Toggle,请选择+ | UI | Toggle。
The UI Toggle object is an interactable checkbox with a label. To create a UI Toggle, select + | UI | Toggle.
图 13.1:UI Toggle GameObject 和子对象
Figure 13.1: UI Toggle GameObject and children
默认情况下,UI 切换有两个子项:Background和Label。Background还有一个子项,即Checkmark。
By default, a UI Toggle has two children: a Background and a Label. The Background also has a child, a Checkmark.
Background子项是一个UI Image,它表示Checkmark UI Image 出现的“框” 。Label是一个 UI Text 对象。
The Background child is a UI Image that represents the “box” in which the Checkmark UI Image appears. The Label is a UI Text object.
如果您想要改变框和复选标记的外观,您可以分别改变Background和Checkmark 子项上的 Image 组件的源图像。
If you want to change the appearance of the box and checkmark, you change the source images of the Image components on the Background and Checkmark children, respectively.
父母Toggle 对象有一个Toggle组件。Toggle 组件看起来与 Button 组件非常相似,并且具有许多相同的属性。正如您将在本章中看到的那样,所有可交互 UI 对象的前几个属性都是相同的。组件底部的属性是UI Toggle 对象独有(图 13.2 ):
The parent Toggle object has a Toggle component. The Toggle component looks very similar to the Button component and has many of the same properties. As you’ll see in this chapter, the first few properties of all interactable UI objects are the same. The properties at the bottom of the component are the ones that are exclusive to the UI Toggle object (Figure 13.2):
图 13.2:Toggle 组件的独特属性
Figure 13.2: The Toggle component unique properties
The Is On property determines whether the Toggle is checked or not when it is initialized in the scene.
Toggle Transition属性决定了当切换按钮在打开和关闭之间或选中和未选中之间转换时会发生什么。两个选项是None和Fade。None转换将立即在选中标记图像可见和不可见之间切换,而Fade转换将使选中标记图像淡入淡出。
The Toggle Transition property determines what happens when the toggle transitions between on and off or checked and not checked. The two options are None and Fade. The None transition will instantaneously toggle between the checkmark Image being visible and not visible while the Fade transition will have the checkmark Image fade in and out.
Graphic属性指定将显示复选标记的图像组件。Checkmark 子项的图像组件会自动分配给此属性,但您可以根据需要更改它。
The Graphic property assigns the Image component that will display the checkmark. The Checkmark child’s Image component is automatically assigned to this property, but you can change it if you so desire.
最后一个属性Group指定Toggle Group组件,该组件将定义 Toggle 属于哪个 Toggle Group(如果有)。
The last property, Group, assigns the Toggle Group component that will define which Toggle Group the Toggle belongs to (if any).
Toggle组件的默认事件是“值改变时”事件,如“值改变时(布尔)”部分所示在。
The Toggle component’s default Event is the On Value Changed Event, as seen in the On Value Changed (Boolean) section.
Toggle组件的默认事件是On Value Changed事件,如Toggle 组件的 On Value Changed (Boolean) 部分所示。这每当选择或取消选择切换按钮时,都会触发事件。它可以接受布尔参数。
The Toggle component’s default event is the On Value Changed Event, as seen in the On Value Changed (Boolean) section of the Toggle component. This event will trigger whenever the toggle is selected or deselected. It can accept a Boolean argument.
当公共函数具有布尔参数时,它将在函数的“值改变(布尔)”事件下拉列表中出现两次:一次在静态参数列表中,另一次在动态布尔列表中,如下面的屏幕截图所示:
When a public function has a Boolean parameter, it will appear twice within the function’s dropdown list of On Value Changed (Boolean) events: once within a Static Parameter list and again within the Dynamic bool list, as shown in the following screenshot:
图 13.3:ToggleWithParameter 方法的静态和动态版本
Figure 13.3: The static and dynamic versions of the ToggleWithParameter method
如果从“静态参数”列表中选择了该函数,则复选框将作为事件中的参数出现。然后,事件将仅发送该复选标记的值。在上图所示的示例中,发送到 ToggleWithParameter ()函数的唯一值将是 false(因为复选框被取消选中)。
If the function is selected from the Static Parameters list, a checkbox will appear as an argument within the Event. The event will then only send the value of that checkmark. In the example shown in the preceding screenshot, the only value that will ever be sent to the ToggleWithParameter() function will be false (since the checkbox is deselected).
如果你想将Toggle 的.isOn值传递给脚本,必须从事件函数下拉列表中的动态布尔值(而非静态参数)列表中选择该函数。
If you want to pass the .isOn value of the Toggle to the script, the function must be chosen from the Dynamic bool (not Static Parameters) list in the function dropdown of the event.
为了演示“值改变(布尔)”事件的工作原理,让我们看看以下两个函数在调用时如何响应:
To demonstrate how On Value Changed (Boolean) events work, let’s see how the following two functions respond when called:
公共无效ToggleWithoutParameter(){
Debug.Log("已改变");
}
公共 void ToggleWithParameter(bool 值){
调试.日志(值);
}public void ToggleWithoutParameter(){
Debug.Log("changed");
}
public void ToggleWithParameter(bool value){
Debug.Log(value);
} 在第 13 章场景中的 Toggle 中添加了以下事件:
The following events are added to a Toggle in the Chapter13 scene:
图 13.4:第 13 章场景中的 Toggle 事件示例中的事件
Figure 13.4: Events on the Toggle Event Example in the Chapter13 scene
当取消选择场景内的 Toggle 时,控制台中将打印以下内容:
When the Toggle within the scene is deselected, the following will print in the Console:
已更改 错误的 错误的
changed False False
当选择切换时,控制台中将打印以下内容:
When the Toggle is selected, the following will print in the Console:
已更改 错误的 真的
changed False True
由于第一个事件调用的函数没有一个参数,它总是在Toggle的值改变的时候执行,不管执行的时候Toggle的值是什么。
Since the function called from the first event does not have a parameter, it will always execute when the value of the Toggle changes, regardless of what the value of the Toggle is when executed.
第二个事件将始终打印False的值,因为该函数有一个参数,并且由于该事件是从“静态参数”列表中选择的,因此发送的参数由复选框表示,该复选框设置为False。 因此,始终将值False发送给该函数。
The second event will always print the value of False, because the function has a parameter and, since the event was chosen from the Static Parameters list, the argument being sent is represented by the checkbox, which is set to False. So, the value False is always sent to the function.
第三个事件的函数是从动态布尔列表中选择的,因此发送给该函数的参数将是Toggle chan的.isOn值ges。
The third event’s function was chosen from the Dynamic bool list, so the argument sent to the function will be the .isOn value to which the Toggle changes.
Toggle Group组件让你有许多 UI Toggle 协同工作,但一次只能选择或打开一个。当 Toggle 位于同一个 Toggle Group 中时,选择一个 Toggle 将关闭所有其他 Toggle。为了让 Toggle Group 在开始时正常工作,您应该将 Toggle group 内的所有 Toggle 的Is On属性设置为False,或者仅将单个 Toggle 的Is On属性设置为True。
The Toggle Group component allows you to have many UI Toggles that work together, where only one can be selected or on at a time. When Toggles are in the same Toggle Group, selecting one Toggle will turn off all others. For the Toggle Group to work properly at the start, you should either set all the Toggles within the Toggle group’s Is On property to False or set only a single Toggle’s Is On property to True.
Toggle Group组件不会创建可渲染的 UI 对象,因此将其附加到空的 GameObject 不会创建任何可见元素。
The Toggle Group component does not create a renderable UI object, so attaching it to an empty GameObject will not create any visible element.
一旦Toggle Group组件附加到 GameObject,就必须将其附加到的 GameObject 拖到组内包含的每个 Toggle 的Group属性中:
Once the Toggle Group component is attached to a GameObject, the GameObject it is attached to must be dragged into the Group property of each of the Toggles that will be contained within the group:
图 13.5:Toggle Group 组件属性
Figure 13.5: The Toggle Group component properties
Toggle Group组件上只有一个属性:Allow Switch Off。Allow Switch Off属性允许在 Toggles 处于开启状态时将其关闭(如果已选择)。请记住,Toggle Group 组件一次最多强制开启一个 Toggles。因此,Allow Switch Off属性被关闭时,会强制始终至少选择一个 Toggles 。
There is only one property on the Toggle Group component: Allow Switch Off. The Allow Switch Off property allows the Toggles to be turned off if they are selected when already in the on state. Remember that the Toggle Group component forces at most one Toggle on at a time. So, the Allow Switch Off property being turned off forces there to be at least one Toggle selected at all times.
我的建议使用此组件时,需要使用一个空的 GameObject 作为要组合在一起的所有 Toggle 的父级。然后,此空的 GameObject 将包含 Toggle Group 组件(如第 13 章场景中的Toggle Group 示例中所示)。然后必须将包含 Toggle Group 组件的对象分配给每个Toggle 组件的Toggle组件上的Group属性小孩。
My suggestion when using this component is to use an empty GameObject that acts as the parent for all the Toggles you wish to group together. This empty GameObject will then contain the Toggle Group component (as demonstrated in the Toggle Group Example in the Chapter13 scene). The object containing the Toggle Group component must then be assigned to the Group property on the Toggle component of each of the Toggle children.
UI 滑块对象允许用户沿路径拖动手柄。路径上的位置对应于一系列值。
The UI Slider object allows the user to drag a handle along a path. The position on the path corresponds to a range of values.
到创建 UI Slider,选择+ | UI | Slider。默认情况下,UI Slider 有三个子项:Background、Fill Area和Handle Slide Area。Fill Area 也有一个子项 Fill,Handle Slide Area 有一个子项 Handle。
To create a UI Slider, select + | UI | Slider. By default, a UI Slider has three children: a Background, a Fill Area, and a Handle Slide Area. The Fill Area also has a child, Fill, and the Handle Slide Area has a child, Handle.
Background 子项是一个 UI Image,表示 Slider 的 Handle 可以遍历的整个区域。在默认的 Slider 示例中,这是被填充的深灰色背景区域。
The Background child is a UI Image that represents the full area that the Slider’s Handle can traverse. In the default Slider example, this is the darker gray background area that gets filled.
Fill Area 子项是一个空的游戏对象。其主要目的是确保其子项 Fill 正确对齐。Fill 是一个 UI 图像,它根据 Slider 的值延伸到 Fill Area。在默认的 Slider 示例中,这是拖在手柄后面并填充背景的浅灰色区域。
The Fill Area child is an empty GameObject. Its main purpose is to ensure that its child, the Fill, is correctly aligned. The Fill is a UI Image that stretches across the Fill Area based on the Slider’s value. In the default Slider example, this is the light gray area that trails behind the handle and fills in the Background.
Handle Slide Area 子对象也是一个空的游戏对象。它的目的是确保其子对象 Handle 的位置和对齐正确。Handle 也是一个 UI Image。Handle 代表Slider的可交互区域。
The Handle Slide Area child is also an empty GameObject. Its purpose is to ensure that its child, the Handle, is correctly positioned and aligned. The Handle is also a UI Image. The Handle represents the interactable area of the Slider.
如果要更改滑块的外观,可以更改背景、填充和手柄上图像组件的源图像孩子们。
If you want to change the appearance of the Slider, you change the Source Image of the Image components on the Background, Fill, and Handle children.
父母Slider 对象有一个Slider组件。它具有所有可交互 UI 对象所共有的属性以及一些 Slider 独有的属性,如下图所示:
The parent Slider object has a Slider component. It has all the properties common to the interactable UI objects along with a few that are exclusive to Sliders, as highlighted in the following figure:
图 13.6:Slider 组件的独特属性
Figure 13.6: The unique properties of the Slider component
填充矩形属性分配对象的 Rect Transform显示填充区域的图像。默认情况下,这是 Fill GameObject 的 Transform 组件。您会注意到,在 Fill 的 Rect Transform 组件上,会显示一条消息,指出某些值由 Slider 驱动。这表示这些值是根据Slider组件更改的。在播放场景时,如果您移动 Slider 的 Handle,您将看不到 Fill 的 Rect Transform 属性更新。但是,如果您在Game视图中移动 Handle 时使 Scene 视图可见,您将看到 Fill 的 Rect Transform 区域随着您影响滑块而发生变化。
The Fill Rect property assigns the Rect Transform of the object that displays the Image of the filled area. By default, this is the Fill GameObject’s Transform component. You’ll note that on the Rect Transform component of the Fill, a message stating Some values driven by Slider is displayed. This indicates that the values are changed based on the Slider component. While playing the scene, if you move the Handle of the Slider, you will not see the Rect Transform properties of the Fill update. However, if you make the Scene view visible while moving the Handle in the Game view, you will see the Rect Transform area of the Fill change as you affect the slider.
Handle Rect属性分配显示手柄图像的对象的 Rect Transform。默认情况下,将 Handle 的 Rect Transform 分配给此属性。您会注意到,Handle GameObject 上的 Rect Transform 组件也具有由 Slider 驱动的一些值消息,因为 Handle 的位置受到Slider的影响。
The Handle Rect property assigns the Rect Transform of the object that displays the handle’s image. By default, the Rect Transform of Handle is assigned to this property. You’ll note that the Rect Transform component on the Handle GameObject also has the Some values driven by Slider message since the position of the Handle is affected by the Slider.
值的范围Slider 所表示的值由Min Value和Max Value属性决定。您可以为Min Value和Max Value属性分配任何值,甚至是负数。虽然Inspector允许您将Min Value定义为大于Max Value 的数字,但如果您这样做, Slider 将无法正常工作。
The range of values that the Slider represents is determined by the Min Value and Max Value properties. You can assign any value to the Min Value and Max Value properties, even negative numbers. While the Inspector allows you to define the Min Value as a number larger than the Max Value, the Slider will not work properly if you do so.
Direction属性允许您选择 Slider 的方向。可用选项包括从左到右、从右到左、从下到上和从上到下。每个方向的位置顺序代表Slider 值范围的第一个位置(或最小值)和最后一个位置(或最大值) 。
The Direction property allows you to select the orientation of the Slider. The available options are Left To Right, Right To Left, Bottom To Top, and Top to Bottom. The order of the positions in each direction represents the first position (or Min Value) and then the last position (or Max Value) of the Slider’s value range.
如果选择了“整数”属性,则滑块可以表示的值的范围将被限制为整数(非十进制)值。
If the Whole Numbers property is selected, the range of values the Slider can represent will be restricted to integer (non-decimal) values.
笔记
Note
作为一名数学老师,我觉得有必要指出这一点。在数学中,术语“整数”代表所有非负整数(0 到无穷大)。在这里,在滑块组件中,“整数”一词代表所有整数,甚至负整数。因此,如果您和我一样是个数学迷,请不要让这暗示您如果选择了“整数”属性,滑块就不能容纳负值。
As I am a math teacher, I feel the need to point this out. In math, the term Whole Numbers represents all non-negative Integers (0 through infinity). Here, in the Slider component, the term Whole Numbers represents all Integers, even negative ones. So, if you’re a math nerd like me, don’t let this imply to you that the Slider cannot hold negative values if the Whole Numbers property is selected.
Value属性是 Slider 的值。Slider 的 Handle 的位置与此属性相关。Inspector 中 Value 属性旁边的滑块是Slide的一对一表示在场景中。
The Value property is the value of the Slider. The position of the Slider’s Handle is tied to this property. The slider in the Inspector next to the Value property is a one-to-one representation of the Slider in the scene.
Slider 组件的默认事件是“值改变时”事件,如“值改变时(单个)”部分所示Slider 组件的。每当 Slider 的 Handle 移动时,都会触发此事件。它可以接受浮点参数。
The Slider component’s default event is the On Value Changed event, as seen in the On Value Changed (Single) section of the Slider component. This event will trigger whenever the Slider’s Handle is moved. It can accept a float argument.
如果您希望将 Slider 的值作为参数发送给具有参数的函数,则必须从动态浮点列表中选择该函数(类似于从 Toggle 的动态布尔列表中选择函数)。
If you want the Slider’s value to be sent as an argument to a function that has a parameter, you must select the function from the Dynamic float list (similar to selecting functions from the Toggle’s Dynamic bool list).
以下函数和屏幕截图代表在 Chapter7Text 场景中找到的 Slider 示例,它触发调用带参数和不带参数的函数的事件:
The following functions and screenshot represent a Slider example found in the Chapter7Text scene that triggers events that call functions with and without parameters:
public void SliderWithoutParameter(){
Debug.Log("已改变");
}
public void SliderWithParameter(浮点值){
调试.日志(值);
}public void SliderWithoutParameter(){
Debug.Log("changed");
}
public void SliderWithParameter(float value){
Debug.Log(value);
} 在下面的屏幕截图中,第三个选项显示从动态浮点列表中选择的函数,并将Value属性的值作为参数发送给该函数:
In the following screenshot, the third option shows the function chosen from the Dynamic float list and will send the value of the Value property as an argument to the function:
图 13.7:第 13 章场景中的滑块事件示例
Figure 13.7: Events on Slider Example in the Chapter13 Scene
笔记
Note
值得注意的是,如果选择了“整数”属性,并且滑块只能保存整数值,则此事件调用的函数将接收这些整数作为浮点值。
It’s important to note that if the Whole Numbers property is selected and the Slider can only hold integer values, the functions called by this event will receive those integers as float values.
现在我们已经回顾了如何使用滑块,让我们回顾一下如何使用两种类型的下拉菜单。
Now that we’ve reviewed how to use Sliders, let’s review how to use the two types of Dropdowns.
有有两个可用的 Dropdown UI 对象,即 Unity 中打包的 UI Dropdown 对象和 Dropdown—TextMeshPro 对象。这两个 Dropdown 对象都允许用户从选项列表中进行选择。当 Dropdown被点击。从列表中选择一个对象后,列表将折叠,使所选选项在下拉菜单中可见(如果需要)。
There are two Dropdown UI objects available, the UI Dropdown object packaged in Unity and the Dropdown—TextMeshPro object. Both the Dropdown objects allow the user to select from a list of options. The list becomes visible when the Dropdown is clicked on. Once an object is selected from the list, the list will collapse, making the chosen option visible within the Dropdown (if desired).
这两个 Dropdown 选项的工作方式几乎完全相同。两者之间的唯一区别是 UI Dropdown 使用 UI Text 对象显示文本,而 Dropdown—TextMeshPro 使用 Text - TextMeshPro 对象。因此,我将在本节中同时讨论这两个对象。此外,由于这两个对象的功能相同,如果您想包含“花哨”的文本,则需要使用 Dropdown—TextMeshPro 而不是 UI Dropdown。
The two Dropdown options are pretty much identical in the way they work. The only difference between the two is the UI Dropdown uses UI Text objects to display text while the Dropdown—TextMeshPro uses Text - TextMeshPro objects. Due to this, I will discuss the two objects at the same time in this section. Additionally, because the two objects are identical in function, you will need to use Dropdown—TextMeshPro over the UI Dropdown if you want to include “fancy” text.
到要创建 UI Dropdown,请选择+ | UI | Dropdown。要创建 Dropdown—TextMeshPro,请选择+ | UI | Dropdown - TextMeshPro 。如您在以下屏幕截图 (图 13.8 )中看到的,两个 Dropdown 对象具有相同的父/子对象关系和名称。默认情况下,Dropdown 对象有三个子对象:标签、箭头和模板。默认情况下,模板子对象处于禁用状态(因此,它在层次结构中显示为灰色),并且有多个子对象。
To create a UI Dropdown, select + | UI | Dropdown. To create a Dropdown—TextMeshPro, select + | UI | Dropdown - TextMeshPro. As you can see in the following screenshot (Figure 13.8), the two Dropdown objects have identical parent/child object relationships and names. By default, the Dropdown objects have three children: a Label, an Arrow, and a Template. The Template child is disabled by default (hence, it appears grayed out in the Hierarchy) and has multiple children.
本章的下拉模板部分讨论了模板子项及其所有子项。
The Template child and all of its children are discussed in the Dropdown Template section of this chapter.
图 13.8:两种下拉菜单的层次结构
Figure 13.8: The Hierarchy of the two types of dropdowns
在下列的段落中,我将讨论所有 Text 对象,就好像它们是 UI Text 对象一样。但是,请记住 Dropdown—TextMeshPro 使用 TextMeshPro - Text 对象。
In the following paragraphs, I will discuss all Text objects as if they are UI Text objects. However, remember that the Dropdown—TextMeshPro uses TextMeshPro - Text objects.
Label 子项是 UI Text 对象。默认情况下,它显示 Dropdown 对象中表示所选选项的文本。当玩家更改所选选项时, Label 的Text组件的Text属性将更改为相应的选项。要更改 Dropdown 的方框区域内显示的文本的属性,请更改Label 上的Text组件的属性。当新文本替换 Label 中的文本时,它将根据Label 的 Text 组件设置的属性自动显示。
The Label child is a UI Text object. By default, it displays the text within the Dropdown object that represents the selected option. As the player changes the selected option, the Text property of the Text component of Label changes to the appropriate option. To change the properties of the text that displays within the boxed area of the Dropdown, change the properties of the Text component on the Label. When new text replaces the text within the Label, it will automatically display based on the properties set by the Text component of the Label.
Arrow 子项是 UI Image。它的唯一功能是保存 Dropdown 右侧(默认情况下)显示的箭头的图像。此箭头实际上不执行任何操作,只是一张图片。它不接受输入,也不会随 Dropdown组件的属性而变化。
The Arrow child is a UI Image. Its only function is to hold the image for the arrow that (by default) appears at the right of the Dropdown. This arrow doesn’t actually do anything and is simply an image. It doesn’t accept inputs or change with the properties of the Dropdown component.
Dropdown 的背景图像位于主 Dropdown 父对象上,而不是名为 Background 的子对象上。因此,如果要更改 Dropdown 的背景和 Arrow 的外观,请分别更改Dropdown 父对象和 Arrow 子对象上的 Image 组件的源图像。Dropdown 的图像仅影响可以选择以显示下拉菜单的矩形。当玩家与下拉菜单交互时向外扩展的菜单背景由模板处理(将在下面的Dropdow中讨论)n模板部分)。
The background image of the Dropdown is on the main Dropdown parent object and not on a child named Background. Therefore, if you want to change the appearance of the Dropdown’s background and Arrow, you change the Source Images of the Image components on the Dropdown parent and Arrow child, respectively. The image of the Dropdown only affects the rectangle that can be selected to display the dropdown menu. The background to the menu that expands outward when the player interacts with the dropdown is handled by the Template (discussed in the following Dropdown Template section).
前我们讨论了 Dropdown 组件的各种属性,让我们更仔细地看看Dropdown 的模板。
Before we discuss the various properties of the Dropdown component, let’s look more closely at Dropdown’s Template.
的孩子名为“模板”的下拉菜单允许您设置下拉菜单中将作为选项出现的“项目”的属性。它还允许您设置菜单背景的属性以及当列表扩展超出下拉菜单的可视区域时出现的滚动条。
The child of Dropdown named Template allows you to set the properties of the “items” that will appear as options in the dropdown menu. It also allows you to set the properties of the background of the menu and the Scrollbar that will appear if the list expands past the viewable area of the dropdown menu.
请记住,默认情况下模板子项是禁用的。启用模板(通过在其检查器中选中复选框)将在场景中显示模板。
Remember that the Template child is disabled by default. Enabling the Template (by selecting the checkbox in its Inspector) will display the Template in the scene.
图 13.9:启用下拉菜单的模板游戏对象
Figure 13.9: Enabling the Template GameObject of the Dropdown
您可以在编辑器中永久启用此功能,因为一旦进入播放模式,它就会按预期隐藏。
You can leave this permanently enabled in your Editor because once you enter Play mode, it will hide as it is supposed to.
如果仔细观察层次结构中的模板的父/子关系,您会注意到它只是一个带有一个滚动条的 UI 滚动视图对象:
If you look closely at the parent/child relationships of the Template within the Hierarchy, you’ll note that it is simply a UI Scroll View object with one Scrollbar:
图 13.10:下拉菜单中带有一个滚动条的 UI 滚动视图
Figure 13.10: The UI Scroll View with One Scrollbar within the Dropdown
查看Template GameObject 的Inspector显示,它实际上只是一个 UI Scroll View 对象,因为它附加了一个Scroll Rect组件没有指定水平滚动条:
Viewing the Inspector of the Template GameObject, shows that it, in fact, is just a UI Scroll View object, as it has a Scroll Rect component attached to it with no Horizontal Scrollbar assigned:
图 13.11:模板的滚动矩形组件
Figure 13.11: The Scroll Rect component of the Template
的内容模板滚动视图对象有一个名为Item 的子项。Item有三个子项:Item Background、Item Checkmark和Item Label。如果你查看Item的检查器,你会发现它只是一个 UI Toggle,并且具有与本章开头讨论的 UI Toggle 相同的子项和属性。
The Content of the Template Scroll View object has a single child named Item. Item has three children: Item Background, Item Checkmark, and Item Label. If you look at the Inspector of Item, you’ll see that it is just a UI Toggle and has the same children and properties as the UI Toggles discussed at the beginning of this chapter.
因此,所有模板都是一个滚动视图,带有一个滚动条和一个切换按钮作为其内容!它最初看起来要复杂得多,但在你分解各个部分之后,你会意识到它只是我们已经讨论过的几个 UI 项目的组合!
So, all Template is a Scroll View with a single Scrollbar and with a single Toggle as its Content! It looks way more complicated initially, but after you break down what the individual pieces are, you›ll realize that it›s just a combination of a few of the UI items we have already discussed!
图 13.12:模板子项的细分
Figure 13.12: Breakdown of the Template’s children
什么时候在职的使用下拉模板,如果您想更改视觉属性和设置,只需记住上图所示的细分,编辑它的前景就会变得没那么可怕。
When working with the Dropdown Template, if you want to change the visual properties and the settings, just remember the breakdown shown in the preceding figure, and the prospect of editing it will seem a lot less daunting.
您设置的下拉菜单中显示的每个项目选项都将遵循与您设置的选项完全相同的视觉属性为项目切换 (Item Toggle) 进行设置。
Every item option you set to appear within the Dropdown will follow the exact same visual properties of those you set for the Item Toggle.
现在我们已经分解模板,我们可以看看Dropdown组件的属性。
Now that we’ve broken down the Template, we can look at the properties of the Dropdown component.
父 Dropdown 对象有一个Dropdown(或 Dropdown - TextMeshPro)组件。它具有其他可交互 UI 对象共有的所有属性,以及Dropdown 独有的一些属性:
The parent Dropdown object has a Dropdown (or Dropdown - TextMeshPro) component. It has all the properties common to the other interactable UI objects, along with a few that are exclusive to Dropdowns:
图 13.13:两个 Dropdown 组件的区别
Figure 13.13: The difference between the two Dropdown components
正如你可以看到从上图可以看出,UI Dropdown 和 Dropdown - TextMeshPro 的属性几乎完全相同。主要区别只有两个。首先,UI Dropdown 对象使用 UIText对象,而Dropdown-TextMeshPro对象使用Text-TextMeshPro对象。其次,Dropdown-TextMeshPro有一个Placeholder属性。
As you can see from the preceding image, the properties of the UI Dropdown and Dropdown - TextMeshPro are nearly identical. There are only two main differences. First, UI Dropdown objects use UI Text objects, while Dropdown - TextMeshPro objects use Text - TextMeshPro objects. Second, Dropdown - TextMeshPro has a Placeholder property.
Dropdown 组件实际上非常强大。它处理与下拉菜单的所有交互,并将切换文本显示、打开和关闭下拉菜单以及在下拉菜单内移动复选框。它甚至添加了滚动条和手柄,以允许下拉菜单拥有非常长的列表。您唯一需要编写的代码是如何解释玩家选择的属性。
The Dropdown component is actually super powerful. It handles all interactions with the Dropdown menu and will switch text displays, open and close the dropdown, and move around the checkbox within the dropdown. It even adds a scrollbar and handle to allow the dropdown menu to have a really long list. The only thing that must be coded by you is how to interpret the property the player selects.
Let’s review the various properties of the two Dropdown objects.
There are two properties related to the caption or the option that is currently selected (Figure 13.3).
Caption Text属性引用 GameObject 的 Text 组件,该组件将显示当前选定选项的文本。默认情况下,这是 Label 子项的 Text 组件。Caption Text是可选的,因此如果您不希望当前选定的选项显示在下拉区域内(而只显示在下拉列表中),只需将Caption Text属性更改为None (Text)即可。
The Caption Text property references the Text component of the GameObject that will display the currently selected option’s text. By default, this is the Text component of the Label child. The Caption Text is optional, so if you do not want the currently selected option displayed within the Dropdown area (and only within the Dropdown list), simply change the Caption Text property to None (Text).
Caption Image属性保存 GameObject 的 Image 组件,该组件将显示当前选定选项的图像。默认情况下,此属性未分配任何内容,您会注意到 Dropdown没有可以保存 Image 的子项。要让图像与文本一起显示,您必须创建一个 UI Image 并将其分配给Caption Image属性。最好创建您创建的 UI Image作为Dropdown 的子项。
The Caption Image property holds the Image component of the GameObject that will display the currently selected option’s image. Nothing is assigned to this property by default and, you will note that the Dropdown does not have a child that can hold the Image. To have an image display with the text, you will have to create a UI Image and assign it to the Caption Image property. It is best that the UI Image you create is created as a child of the Dropdown.
有三个属性与将模板的属性分配给下拉列表中显示的所有可能选项相关。
There are three properties related to assigning the template’s properties to all possible options to display in the dropdown list.
Template属性引用模板的 Rect Transform。如前所述,模板定义下拉列表中每个选项的外观以及下拉框的外观。默认情况下,此属性分配给子Template 对象。
The Template property references the Rect Transform of the template. As stated previously, the template defines the way each option within the dropdown list will look as well as how the dropdown holder will look. By default, this property is assigned to the child Template object.
Item Text属性引用GameObject 的Text组件,该组件包含项目模板的文本。默认情况下, Item Label(Item 的子项)上的Text组件被分配给此属性。
The Item Text property references the Text component of the GameObject that holds the text of the item template. By default, the Text component on the Item Label (child of the Item) is assigned to this property.
Item Image属性引用GameObject 的Image组件,该组件保存项目模板的图像。默认情况下,此属性未分配,类似于Caption Image 。与Caption Image一样,要使用此属性,需要创建一个 UI Image 并将其分配给此属性。如果创建一个,请确保将其添加为Templ 中Item的子项吃孩子以避免混淆。
The Item Image property references the Image component of the GameObject that holds the image of the item template. By default, this property is unassigned, similar to the Caption Image. Just as with the Caption Image, to use this property, a UI Image will need to be created and assigned to this property. If you create one, ensure that you add it as a child of Item within the Template child to avoid confusion.
Value属性表示当前选择了哪个选项。选项位于列表中,Value属性中的数字表示当前所选选项在列表中的索引。由于选项由其索引表示,因此第一个选项的 Value为0(而不是 1)。
The Value property represents which option is currently selected. The options are in a list, and the number in the Value property represents the currently selected option’s index within the list. Since the options are represented by their indices, the first option has a Value of 0 (not 1).
Options属性列出了下拉菜单中的所有选项。在列表中,每个选项都有一个文本字符串和精灵(可选)。此列表中的所有字符串和精灵将根据 Dropdown 组件的属性自动交换为 Dropdown 子项的正确组件属性。因此,您无需编写任何代码来确保玩家与Dropdown 交互时这些项目能够正确显示。
The Options property lists out all the options within the Dropdown menu. Within the list, each option has a text string and sprite (optional). All strings and sprites within this list will automatically swap into the correct component properties of the children of Dropdown, based on the properties of the Dropdown component. So, you will not have to write any code to ensure that these items display appropriately when the player interacts with the Dropdown.
默认情况下,选项列表包含三个选项。但是,您可以通过选择列表底部的加号和减号来添加或减去选项。您还可以通过拖放选项的手柄(两条水平线)来重新排列列表中的选项。请注意,重新排列列表中的选项将更改它们在列表中的索引,然后更改它们的值结束Dropdown组件。
By default, the Options list contains three options. However, you can add or subtract options by selecting the plus and minus sign at the bottom of the list. You can also rearrange the options within the list by dragging and dropping the options’ handles (two horizontal lines). Note that rearranging the options in the list will change their indices within the list and then change the Value they send to the Dropdown component.
这下拉组件处理与下拉菜单本身的所有交互。您唯一需要编写的代码是如何解释玩家选择的选项。
The Dropdown component handles all interactions with the Dropdown menu itself. The only thing that has to be coded by you is how to interpret the option the player selected.
Dropdown 组件的默认事件是On Value Changed事件,如Dropdown组件的On Value Changed (Int32)部分所示。每当玩家选择新选项时,此事件都会触发。它接受整数作为参数,并且与本章讨论的其他事件一样,您可以选择不传递参数、传递静态参数或传递动态参数。
The Dropdown component’s default event is the On Value Changed Event, as seen in the On Value Changed (Int32) section of the Dropdown component. This event will trigger whenever a new option is selected by the player. It accepts an integer as an argument and, as with the other events discussed in this chapter, you can choose to pass no argument, a static argument, or a dynamic argument.
如果要将所选选项的索引(或Value属性的值)发送给函数,则可以使用Dynamic int列表中的 Int32 参数将其发送给函数。请参阅本文末尾的创建带图像的下拉菜单示例以了解此方法的实现。
If you want to send the index of the option selected (or the value of the Value property) to a function, you›d send it to a function with a Int32 parameter from the Dynamic int list. Refer to the Creating a dropdown menu with images example at the end of the text for an implementation of this.
我们将要介绍的下一个可交互 UI 组件l review 是 UI输入字段。
The next interactable UI component we’ll review is the UI Input Field.
The UI Input Field provides a space in which the player can enter text.
要创建 UI 输入字段,请选择+ | UI |输入字段。默认情况下,InputField GameObject 具有两个子对象:一个占位符和一个文本对象。
To create a UI Input Field, select + | UI | Input Field. By default, the InputField GameObject has two children: a Placeholder and a Text object.
Placeholder 子项是一个 UI Text 对象,表示在玩家输入任何文本之前显示的文本。一旦玩家开始输入文本,Placeholder GameObject 上的 Text 组件就会停用,使文本不再可见。默认情况下,Placeholder 显示的文本是Enter text…,但显示的文本及其属性可以通过影响 Placeholder的Text组件上的属性轻松更改。
The Placeholder child is a UI Text object that represents the text displayed before the player has input any text. Once the player begins entering text, the Text component on the Placeholder GameObject deactivates, making the text no longer visible. By default, the text displayed by the Placeholder is Enter text…, but the text being displayed as well as its properties are easily changed by affecting the properties on the Text component of the Placeholder.
Text 子项是一个 UI Text 对象,用于显示玩家输入的文本。设置 Text 对象的Text组件的属性将更改玩家输入的文本的显示。
The Text child is a UI Text object that displays the text the player inputs. Setting the properties on the Text object’s Text component will change the display of the text entered by the player.
InputField 包含一个Image组件。如果要更改输入框的外观,请更改IInputField上的mage组件。
InputField contains an Image component. If you want to change the appearance of the input box, change the Source Image of the Image component on the InputField.
这父 InputField 对象有一个输入字段组件。它具有可交互 UI 对象所共有的所有属性以及一些输入字段独有的属性:
The parent InputField object has an Input Field component. It has all the properties common to the interactable UI objects along with a few that are exclusive to Input Fields:
Figure 13.14: The unique properties of the Input Field component
让我们看看 UI输入字段的各种属性。
Let’s look at the various properties of the UI Input Field.
许多输入字段组件中的属性会影响输入字段中显示的文本。由于某些属性有很多选项和相关信息,我将按略微不按顺序进行讨论。
Many of the properties within the Input Field component affect the text that displays within the Input Field. Due to some of the properties having a lot of options and information pertaining to them, I will discuss them slightly out of order.
请记住,要更改输入文本的视觉样式,您需要更改Text GameObject上的Text组件的属性。
Remember that to change the visual style of the entered text, you need to change the properties of the Text component on the Text GameObject.
Text Component 属性引用 GameObject 的 Text 组件,该组件将显示玩家输入的文本。默认情况下,这是Text子项的Text组件。
The Text Component property references the Text component of the GameObject that will display the player’s entered text. By default, this is the Text component of the Text child.
Text属性是当前输入到输入字段中的文本。当您尝试从输入字段检索数据时,您希望从此属性获取信息,而不是从文本游戏对象上的文本组件获取信息。文本游戏对象上的文本组件将仅存储当前显示的内容。因此,如果文本由于是密码或已滚动而显示为星号,则完整且正确的文本将不会存储在文本游戏对象的文本组件中。
The Text property is the text currently entered into the Input Field. When you are attempting to retrieve the data from the Input Field, you want to get the information from this property and not from the Text component on the Text GameObject. The Text component on the Text GameObject will only store what is currently being displayed. So, if the text is displayed as asterisks because it’s a password or has scrolled, the full and correct text will not be stored in the Text component of the Text GameObject.
字符限制属性允许您指定玩家可以在字段中输入的最大字符数。将“字符限制”属性设置为0可允许无限制的文本输入。
The Character Limit property allows you to specify the maximum number of characters the player can enter into the field. Leaving the Character Limit property set to 0 allows unlimited text entry.
占位符属性引用GameObject 的Text组件,当玩家未输入任何内容或清除所有输入的文本时显示文本。默认情况下,这是Placeholder子项的Text组件。
The Placeholder property references the Text component of the GameObject that displays the text when the player has either not entered anything or has cleared all entered text. By default, this is the Text component of the Placeholder child.
隐藏移动输入属性允许您覆盖选择文本输入字段时弹出的默认移动键盘。如果您有自己的键盘并希望播放器使用,则可选择此选项。目前,这仅适用于iOS 设备。
The Hide Mobile Input property allows you to override the default mobile keyboard that pops up when a text Input Field is selected. You will select this option if you have your own keyboard that you want the player to use. Currently, this only works for iOS devices.
如果您想在 Android 设备上使用自己的键盘,最好的办法是创建自己的自定义输入字段脚本。该脚本会在选择输入框时显示键盘,然后根据自定义键盘的按键更改框内的文本。
If you wanted to use your own keyboard on an Android device, your best bet would be to create your own custom input field script. The script would show a keyboard when the input box is selected and then change the text within the box based on the custom keyboard key presses.
只读属性使输入字段内的文本保持静态,玩家无法编辑。激活此属性后,玩家仍然可以选择文本进行复制和粘贴。
The Read Only property makes the text within the Input Field static and uneditable by the player. The player can still select the text to copy and paste it when this property is activated.
什么时候选择只读属性后,仍可以通过访问输入字段组件上的Text属性来通过代码编辑输入字段显示的文本。但是,更改文本 GameOb 的Text组件上的Text属性ject,不会改变显示的文本。
When the Read Only property is selected, the text displayed by the Input Field can still be edited via code by accessing the Text property on the Input Field component. However, changing the Text property on the Text component of the Text GameObject, will not change the displayed text.
内容类型属性确定输入字段将接受的字符类型。在屏幕上显示键盘的设备上,它还会影响选择输入字段时设备显示的键盘。如果所需的键盘不可用,则将显示默认键盘。例如,如果设备没有仅数字键盘,它将显示默认键盘。有关每个键盘和这些内容类型附带的字符验证的更详细说明,请参阅键盘类型和字符验证选项部分。
The Content Type property determines the types of characters that will be accepted by the Input Field. On devices that display keyboards on screen, it also affects the keyboard that is displayed by the device when the input field is selected. If the desired keyboard is not available, the default keyboard will be displayed. For example, if the device does not have a numbers-only keyboard, it will display the default keyboard. For more detailed explanations of each keyboard and character validations that come with these Content Types, refer to the Keyboard Types and Character Validation Options sections.
可能的选项有标准、自动更正、整数、十进制数、字母数字、姓名、电子邮件地址、密码、PIN和自定义。
The possible options are Standard, Autocorrected, Integer Number, Decimal Number, Alphanumeric, Name, Email Address, Password, Pin, and Custom.
标准选项允许输入任何字符。但请注意,输入的文本字体不支持的字符将不会显示。
The Standard option allows any character to be entered. Note, however, that characters not available for the entered text’s font will not display.
自动更正选项的工作方式与标准选项类似,但在带有屏幕键盘(尤其是触摸屏键盘)的设备上,它允许设备的自动更正功能根据其自身的自动更正算法自动覆盖单词。
The Autocorrected option works like the Standard option, but on devices with onscreen keyboards (particularly touchscreen keyboards), it allows the device’s autocorrect functionality to automatically override words based on its own autocorrecting algorithms.
整数选项仅允许整数值(无小数的正数和负数)。玩家将被限制输入多个负号。小数选项的工作原理类似,但它也接受小数点。玩家将被限制输入多个小数点。在带有屏幕键盘的设备(尤其是移动设备)上,这两个选项将显示数字键盘,而不是标准键盘。
The Integer Number option allows only integer values (positive and negative numbers without decimals). The player will be restricted from entering more than one negative symbol. The Decimal Number option works similarly, except that it also accepts decimal points. The player will be restricted from entering more than one decimal point. On devices with onscreen keyboards (particularly mobile devices), the numeric keyboard will appear rather than the standard keyboard with these two options.
字母数字选项仅允许字母(大写和小写)以及数字和输入。不接受数学符号和标点符号,包括负数和小数点(句点)。
The Alphanumeric option only allows letters (uppercase and lowercase) along with numbers and input. Mathematical symbols and punctuation, including negative numbers and decimal points (periods), are not accepted.
名称选项将自动将字段中输入的每个新单词大写。玩家可以选择将单词的首字母小写,方法是删除该字母,然后以小写形式重新输入。
The Name option will automatically capitalize each new word entered within the field. The player has the option to lowercase the first letter of a word by deleting the letter and re-entering it in lowercase.
电子邮件地址选项将允许玩家输入电子邮件地址。它还将限制玩家输入多个 @ 符号或两个连续的句点(点/小数)。
The Email Address option will allow the player to enter an email address. It will also restrict the player from entering more than one @ symbol or two consecutive periods (dots/decimals).
密码选项允许在字段中输入字母、数字、空格和符号。当玩家在密码输入字段中输入文本时,输入的文本将隐藏起来并显示为星号 ( * )。
The Password option allows letters, numbers, spaces, and symbols entered in the field. When the player enters text into a Password Input Field, the entered text will be hidden from view and displayed as asterisks (*).
Pin选项仅允许输入整数(无小数)。负数也可以接受。玩家在Pin 内容类型字段中输入的文本将被隐藏,就像密码选项隐藏玩家输入一样。在屏幕键盘设备上,数字键盘将与Pin选项一起显示。
The Pin option allows only integer numbers (no decimals) to be entered. Negative numbers are accepted. The text entered by the player in a field with the Pin Content Type will be hidden in the same way the Password option hides the player input. On an onscreen keyboard device, the numeric keyboard will be displayed with the Pin option.
最后一个选项“自定义”可让您最大程度地控制输入类型。选中后,检查器中会出现新属性,允许您选择线型、输入类型e、键盘类型和字符验证。
The final option, Custom, gives you the most control of the type of input. When selected, new properties appear in the Inspector allowing you to select the Line Type, Input Type, Keyboard Type, and Character Validation.
线型选项适用于内容类型的标准、自动更正和自定义选项。行类型选项有三种:单行、多行提交和多行换行。所有其他内容类型自动限制为单行类型。如果允许玩家输入的文本多于输入字段的可见区域可以显示的文本(即字符限制属性不将其限制在可见空间内),则文本将根据所选的行类型滚动。
The Line Type option is available with the Standard, Autocorrect, and Custom options for Content Type. There are three Line Type options: Single Line, Multi Line Submit, and Multi Line New Line. All other Content Types are automatically restricted to Single Line types. If the player is allowed to enter more text than the Input Field’s visible area can display (meaning the Character Limit property does not restrict it to the visible space), the text will scroll based on the Line Type selected.
单行选项仅允许输入的文本显示在一行上。如果文本超出可见范围水平空间,文本将水平滚动。如果玩家按下Enter键,输入字段将表现为文本已提交。
The Single Line option only allows the entered text to be displayed on one line. If the text exceeds the visible horizontal space, the text will scroll horizontally. If the player hits the Enter key, the Input Field acts as if the text has been submitted.
多行提交和多行换行选项允许文本在超出可见水平空间时垂直溢出,在超出可见垂直空间时垂直滚动。这两个选项的区别在于按下Enter键时会发生什么:多行提交将提交文本nd Multi Line New Line将开始新的一行。
The Multi Line Submit and Multi Line New Line options allow the text to overflow vertically if it exceeds the visible horizontal space and scroll vertically if the text exceeds the visible vertical space. The difference between the two options is what happens when the Enter key is hit: Multi Line Submit will submit the text and Multi Line New Line will start a new line.
选择自定义内容类型后,您可以从三种输入类型中进行选择:标准、自动更正和密码。
When the Custom Content Type is selected, you have the option to select from three Input Types: Standard, Autocorrect, and Password.
选择这些不同的输入类型不会改变键盘或提供任何验证,就像名称类似的内容类型一样。例如,密码输入类型将接受Enter键作为多行新行的新行,并在字段中将其显示为星号,但在存储在Text属性中的实际数据中将其接受为新行。
Selecting these various Input Types does not change the keyboard or provide any validation, like the similarly named Content Types. For example, the Password Input Type will accept the Enter key as a new line with Multi Line New Line and display it as an asterisk in the field but accept it as a new line in the actual data stored in the Text property.
标准选项不会对输入类型施加任何特殊情况。
The Standard option does not put any special circumstances on the type of input.
自动更正选项适用于具有内置自动更正功能的屏幕键盘平台。此选项允许设备的自动更正功能根据需要更改文本。
The Autocorrect option applies to platforms with onscreen keyboards that have built-in autocorrect functionality. This option allows the device’s autocorrect to change the text as it sees fit.
The Password option will display the text as asterisks.
选择自定义内容类型后,您可以选择键盘类型。带有屏幕键盘的设备,此属性允许您选择在选择输入字段时显示哪个键盘。
When the Custom Content Type is selected, you have the option to select Keyboard Types. On devices with onscreen keyboards, this property allows you to select which keyboard will display when the Input Field is selected.
可能的选项是默认、支持 ASCII、数字和标点符号、URL、数字键盘、电话键盘、姓名电话键盘、电子邮件地址、社交、搜索、小数键盘和一次性代码。
The possible options are Default, ASCII Capable, Numbers And Punctuation, URL, Number Pad, Phone Pad, Name Phone Pad, Email Address, Social, Search, Decimal Pad, and One Time Code.
如果目标设备上没有所选的键盘,则将显示设备的默认键盘。
If the keyboard selected is not available on the target device, the device’s default keyboard will be displayed.
默认选项显示设备的默认(字母)键盘。在大多数设备上,此键盘仅显示字母、空格键、退格键和回车键(Enter)。选择此选项后,玩家将能够切换到带有数字和标点符号键的键盘。
The Default option displays the device’s default (letters) keyboard. On most devices, this keyboard only displays letters, the Space key, Backspace key, and Return (Enter) key. When this option is selected, the player will have the ability to switch to the keyboard with numbers and punctuation keys.
例如iOS的英文默认键盘和数字及标点符号键盘之间可以轻松切换,如下图所示:
For example, the iOS English default keyboard and numbers and punctuation keyboard can easily be switched between, as shown in the following figure:
图13.15:iOS英文默认键盘以及数字和标点符号键盘
Figure 13.15: The iOS English default keyboard and numbers and punctuation keyboard
ASCII Capable选项显示带有标准 ASCII 键的设备键盘。此选项可用于将键盘限制为英语和类似语言的键盘。此键盘也是字母键盘,并且提供切换到数字和标点符号键盘的选项。例如,上图显示的是 iOS ASCII 键盘,因为它与默认英语键盘相同。
The ASCII Capable option displays the device’s keyboard with standard ASCII keys. This option is available to restrict the keyboard to those of English and similar language keyboards. This keyboard is also a letters keyboard, and the option to switch to the numbers and punctuation keyboard is available. For example, the iOS ASCII keyboard is shown in the preceding diagram, as it is the same as the default English keyboard.
数字和标点符号选项打开设备的数字和标点符号键盘,并可以选择切换到“字母”键盘。例如,iOS 数字和标点符号键盘如上图所示。
The Numbers And Punctuation option opens the device’s numbers and punctuation keyboard with the option to switch to the “letters” keyboard. For example, the iOS numbers and punctuation keyboard is shown in the preceding diagram.
URL 键盘选项会调出设备的 URL 键盘。此键盘有句点 ( . ) 键、正斜杠 ( / ) 键和.com键代替空格键。例如,下图显示了 iOS URL 键盘及其数字/标点符号形式。请注意,URL 键盘的数字/标点符号形式与默认/ASCII 键盘附带的数字和标点符号形式不同:
The URL Keyboard option brings up the device’s URL keyboard. This keyboard has a period (.) key, forward-slash (/) key, and .com key in place of the Space Key. For example, the following image shows the iOS URL keyboard and its numbers/punctuation form. Note that the URL keyboard’s numbers/punctuation form is not the same as the numbers and punctuation form that accompanies the default/ASCII keyboard:
图 13.16:iOS URL 键盘及其数字/标点符号形式
Figure 13.16: The iOS URL keyboard and its numbers/punctuation form
数字键盘选项显示设备的键盘,其中包含数字(0 - 9)和(通常)退格键。此键盘用于 PIN,因此不允许使用替代字符。例如,下图显示了 iOS 数字键盘键盘:
The Number Pad option displays the device’s keyboard with numbers (0-9) and (usually) a Backspace key. This keyboard is used for PINs, so it does not allow alternate characters. For example, the following image shows the iOS number pad keyboard:
图 13.17:数字键盘
Figure 13.17: The number pad keyboard
“电话键盘”选项显示设备的键盘,其按键与数字键盘相同,但还包含星号和井号(磅号)的按键。例如,下图显示了 iOS 电话键盘及其符号显示:
The Phone Pad option displays the device’s keyboard with the same keys as the number pad keyboard but also includes keys for the asterisk and hash sign (pound sign). For example, the following image shows the iOS phone pad keyboard and its symbol display:
图13.18:iOS手机键盘及其符号显示
Figure 13.18: The iOS phone pad keyboard and its symbol display
姓名电话键盘选项显示设备的“字母”键盘,并可以切换到手机键盘。例如,下图显示了 iOS 名称手机键盘的两种视图:
The Name Phone Pad option displays the device’s “letters” keyboard and can switch to the phone pad keyboard. For example, the following image shows the iOS name phone pad keyboard’s two views:
图 13.19:iOS 手机键盘的两种视图
Figure 13.19: The iOS name phone pad keyboard’s two views
电子邮件地址选项显示设备的电子邮件键盘。电子邮件键盘突出显示@键和句点 ( . ) 键以及其他常见的电子邮件地址符号。例如,下图显示了 iOS 电子邮件键盘及其数字/标点符号形式:
The Email Address option shows the device’s email keyboard. The email keyboard prominently displays the @ key and the period (.) key as well as other common email address symbols. For example, the following image shows the iOS email keyboard and its numbers/punctuation form:
图 13.20:iOS 电子邮件键盘及其数字/标点符号形式
Figure 13.20: The iOS email keyboard and its numbers/punctuation form
社交键盘选项显示设备的社交键盘。此键盘突出显示常见的社交网络键,例如@键和#键。例如,下图显示了 iOS“Twitter”键盘及其数字/标点符号形式。在 iOS 设备上,此键盘被专门称为Twitter 键盘,但它会显示在 Instagram 等其他社交网络应用程序上:
The Social Keyboard option displays the device’s social keyboard. This keyboard prominently displays common social networking keys such as the @ key and the # key. For example, the following image shows the iOS “Twitter” keyboard and its numbers/punctuation form. On the iOS device, this keyboard is specifically called the Twitter keyboard, but it displays on other social networking apps such as Instagram:
图 13.21:iOS Twitter 键盘
Figure 13.21: The iOS Twitter keyboard
“搜索”选项显示网络搜索键盘。此键盘突出显示空格键和句点键。例如,下图显示了 iOS 网络搜索键盘及其数字/标点符号形式:
The Search option displays the web search keyboard. This keyboard prominently displays the space and period keys. For example, the following image shows the iOS web search keyboard and its numbers/punctuation form:
图 13.22:iOS 网页搜索键盘及其数字/标点符号形式
Figure 13.22: The iOS web search keyboard and its numbers/punctuation form
笔记
Note
您可以在https://developer.apple.com/documentation/uikit/uikeyboardtype查看 iOS 上可用的所有键盘类型的列表。
You can view a list of all the keyboard types available on iOS at https://developer.apple.com/documentation/uikit/uikeyboardtype.
您可以在https://developer.android.com/reference/android/w查看 Android 上可用的所有输入类型的列表(不仅仅是键盘,但它们也包含在列表中)idget/TextView.xhtml#attr_android:inputType。
You can view a list of all input types (not just keyboard, but they are included in the list) available on Android at https://developer.android.com/reference/android/widget/TextView.xhtml#attr_android:inputType.
什么时候选择自定义内容类型后,您可以选择要使用的字符验证类型。此选项限制了可在输入字段中输入的字符类型。如果玩家尝试输入不符合限制的字符,则不会在输入字段中插入任何字符。
When the Custom Content Type is selected, you have the option to select which type of Character Validation you would like to use. This option restricts the type of characters that can be entered in the Input Field. If the player attempts to enter a character that does not meet the restrictions, no character will be inserted in the Input Field.
可能的选项包括无、整数、十进制、URL、字母数字、名称和电子邮件地址。
The possible options are None, Integer, Decimal, URL, Alphanumeric, Name, and Email Address.
字符验证仅检查输入的每个字符,以查看该字符是否允许在字段中使用。它不会检查整个字符串以查看字符串本身是否有效。例如,如果选择了电子邮件地址,它不会检查它是否实际上是电子邮件地址的格式。这种类型的验证必须通过代码来完成。
Character Validation only checks each individual character being entered to see whether it is allowed within the field. It does not check the entire string to see whether the string itself is valid. For example, if Email Address is selected, it will not check whether it is actually in the format of an email address. That type of validation will have to be accomplished via code.
无选项不执行任何字符验证,允许以任何格式将任何字符输入到输入字段中。
The None option does not perform any character validations, allowing any character to be entered into the Input Field with any formatting.
整数选项允许输入任何正整数或负整数值。这将限制输入仅允许数字0到9和破折号(负号)。输入进一步限制为仅允许将负号作为输入的第一个字符。
The Integer option allows any positive or negative integer value to be entered. This restricts the input to only allowing the digits 0 through 9 and the dash (negative symbol). The input is further restricted to allowing the negative symbol only as the first character entered.
十进制选项具有与整数选项相同的限制,但它还允许输入一个小数点。
The Decimal option has the same restriction as the Integer option, but it also allows a single decimal point to be entered.
字母数字选项仅允许使用英文字母(a 到 z)和数字 0 到 9。允许使用大写和小写字母;不接受负数符号和小数点。
The Alphanumeric option only allows English letters (a through z) and the digits 0 through 9. Capital and lowercase letters are permitted; the negative symbol and decimal point are not accepted.
名称选项允许名称中常见的字符并提供格式。它允许字母、空格和撇号 (')。它还强制将字符串中的第一个字符以及空格后的每个字符大写。空格不能跟在撇号后面,空格也不能跟在另一个空格后面。字符串中只允许一个撇号。字母不限于 az,就像字母数字选项一样。允许使用任何 Unicode 字母。
The Name option allows characters typically found in names and provides formatting. It allows letters, spaces, and an apostrophe (‘). It also enforces capitalization of the first character in the string and every character that comes after a space. A space cannot follow an apostrophe, and a space cannot follow another space. Only one apostrophe is allowed in the string. The letters are not restricted to just a-z as with the Alphanumeric option. Any Unicode letter is permitted.
笔记
Note
有关所有允许的 Unicode 字母的列表,请查看.NET 中Char.IsLetter方法的备注,网址为https://msdn.microsoft.com/en-us/library/system.char.isletter(v=vs.110).aspx。
For a list of all allowable Unicode letters, check out the remarks on the Char.IsLetter method in .NET at https://msdn.microsoft.com/en-us/library/system.char.isletter(v=vs.110).aspx.
电子邮件地址选项允许在电子邮件地址中使用字符,并强制执行一些格式规则。与其他验证选项相比,它对可输入字符类型的限制明显较少。允许使用以下字符:
The Email Address option allows characters that are allowed within an email address and enforces a few formatting rules. It is significantly less restrictive in the types of characters that can be entered than the other validation options. The following characters are allowed:
|
符号名称 Symbol name |
特点 Character |
|
at 符号 at sign |
@ @ |
|
点/句号 dot/period |
。 . |
|
问号 question mark |
? ? |
|
感叹号 exclamation point |
! ! |
|
连字符 hyphen |
- - |
|
下划线 underscore |
_ _ |
|
撇号 apostrophe |
‘ ‘ |
|
反引号 backtick |
` ` |
|
波浪号 tilde |
~ ~ |
|
打开和关闭括号 open and close braces |
{和 } { and } |
|
竖线 vertical bar |
| | |
|
插入符号 caret |
^ ^ |
|
星号 asterisk |
* * |
|
加号 plus sign |
+ + |
|
等号 equal sign |
+ + |
|
斜杠 forward slash |
/ / |
|
井号/磅号 hash sign/pound sign |
# # |
|
美元符号 dollar sign |
$ $ |
|
百分比 percent |
% % |
|
& 符号 ampersand |
& & |
表 13.1:允许的特殊字符
Table 13.1: Permitted special characters
空间是不允许,字符串中只允许一个@符号,并且一个点不能跟在另一个点后面。
Spaces are not allowed, only one @ symbol is allowed in the string, and a dot cannot follow another dot.
尽管点作为电子邮件地址的第一个字符无效,但电子邮件地址字符验证选项不会限制它作为输入字段中输入的第一个字符。
Even though a dot as the first character of an email address is not valid, the Email Address Character Validation option does not restrict it from being the first character entered in the Input Field.
插入符号(也称为文本插入光标)是竖线用来表示键入时将插入文本。游戏进行时,第三个子项将自动添加到 InputField,名为InputField Input Caret。选择输入字段后,插入符号将变为可见。
A caret (also known as a text insertion cursor) is a vertical bar used to represent where text will be inserted when typed. When the game is playing, a third child is automatically added to InputField named InputField Input Caret. When the Input Field is selected, the caret becomes visible.
本节讨论的属性将影响插入符号的外观以及使用插入符号选择(或突出显示)文本时文本的外观。
The properties discussed in this section affect the look of the caret as well as the look of text if it is selected (or highlighted) using the caret.
插入符号闪烁率属性决定插入符号闪烁的速度。分配给此属性的数字表示插入符号每秒闪烁的次数。默认值为0.85。
The Caret Blink Rate property determines how quickly the caret will blink. The number assigned to this property represents how many times the caret will blink per second. The default value is 0.85.
插入符号宽度属性决定插入符号的粗细(以像素为单位)。默认值为1。
The Caret Width property determines how thick the caret is in pixels. The default value is 1.
当选择“自定义插入符号颜色”属性时,辅助属性“插入符号颜色”将变为可用。然后,您可以选择更改插入符号的颜色。除非选择了“自定义插入符号颜色”并更改了插入符号颜色,否则插入符号将为深灰色。
When the Custom Caret Color property is selected, a secondary property, Caret Color, becomes available. You then have the option to change the color of the caret. Unless Custom Caret Color is selected and the Caret Color is changed, the caret will be a dark grey color.
什么时候将插入符号拖到输入字段中的字符上,字符将被选中(或突出显示)。选择颜色属性决定所选文本的颜色xt.
When the caret is dragged across characters within the Input Field, the characters will be selected (or highlighted). The Selection Color property determines the color of the selected text.
这输入字段组件有两个默认事件。第一个默认事件是“值改变时”事件,如输入字段组件的“值改变时(字符串)”部分所示。每当输入字段中的文本发生更改时,都会触发此事件。它接受字符串作为参数,其参数的使用方式与本章前面讨论的 UI 组件中的“值改变时”事件相同。如果要将参数传递给函数,可以从“静态参数”列表或“动态字符串”列表中选择该函数,具体取决于您希望如何或是否要将参数传递给函数:
The Input Field component has two default events. The first default event is the On Value Changed Event, as seen in the On Value Changed (String) section of the Input Field component. This event will trigger whenever the text within the Input Field is changed. It accepts a string as an argument, and its use of the argument works in the same way as the On Value Changed events from UI components discussed earlier in this chapter. If you want to pass a parameter to the function, you can select the function from either the Static Parameters list or from the Dynamic string list, depending on how or if you want an argument passed to the function:
图 13.23:第 13 章场景中输入字段上的值改变事件示例
Figure 13.23: On Value Changed Events on Input Field Example in the Chapter13 scene
如果您想不断检查玩家在输入字段中输入的内容,您可以使用上图所示的第三个设置,即从动态字符串列表中选择一个带有参数的函数。
If you want to constantly check what the player is entering in the Input Field, you would use the third setup shown in the preceding image, which selects a function with a parameter from the Dynamic string list.
第二个默认事件是 On End Edit 事件,如输入字段组件的On End Edit (String)部分所示。每当玩家完成文本编辑时,都会触发此事件。玩家可以通过单击输入字段外部(这样输入就不再被选中)或提交文本来确认此完成。
The second default event is the On End Edit event, as seen in the On End Edit (String) section of the Input Field component. This event fires whenever the player completes editing the text. This completion is confirmed by the player either clicking outside of the Input Field (so that the Input is no longer selected) or by submitting the text.
它接受字符串作为参数。与本章讨论的其他事件一样,您可以选择不传递参数、静态参数或动态参数。以下屏幕截图显示了所有三个选项的设置:
It accepts a string as an argument. As with the other events discussed in this chapter, you can choose to pass no argument, a static argument, or a dynamic argument. The following screenshot shows the setup for all three options:
图 13.24:第 13 章场景中输入字段上的结束编辑事件示例
Figure 13.24: On End Edit Events on Input Field Example in the Chapter13 Scene
如果希望在按下Enter键时调用 On End Edit 事件,请对行类型使用单行或多行提交选项。
If you want to have the On End Edit event called when the Enter key is hit, use either the Single Line or Multi Line Submit options for the Line Type.
现在我们已经查看了 UI 输入字段,让我们查看它的对应项,输入字段 – TextMeshP反之。
Now that we’ve reviewed the UI Input Field, let’s review its counterpart, the Input Field – TextMeshPro.
这输入字段 - TextMeshPro 是与 UI 输入字段非常相似。添加到场景后,您会看到它看起来几乎完全相同,只是占位符文本的字体不同。UI 输入字段默认使用 Arial 字体,而输入字段 - TextMeshPro 使用 Liberation Sans。
The Input Field - TextMeshPro is very similar to the UI Input Field. When added to the scene, you’ll see it looks nearly identical, except that the placeholder text has a different font. The UI Input Field uses an Arial font by default, while the Input Field - TextMeshPro uses Liberation Sans.
要创建 UI 输入字段,请选择+ | UI |输入字段 - TextMeshPro。默认情况下,输入字段 - TextMeshPro 游戏对象有一个名为 Text Area 的子对象,它有两个子对象:一个占位符和一个文本对象。您会发现它的设置与 UI输入字段略有不同。
To create a UI Input Field, select + | UI | Input Field - TextMeshPro. By default, Input Field - TextMeshPro GameObject has a child named Text Area, which has two children: a Placeholder and a Text object. You will observe that it is slightly different in setup than the UI Input Field.
Text Area GameObject 包含一个 Rect Transform 组件和一个 Rect Mask 2D 组件。Text Area 确保文本不会出现在指定区域之外,如下图突出显示的区域所示。如果您想更改此区域的大小,可以更改 Rect Transform 组件上的属性:
The Text Area GameObject contains a Rect Transform component and a Rect Mask 2D component. The Text Area ensures that the text does not appear outside of a specified area, as shown by the highlighted area in the following image. If you wanted to change the size of this area, you would change the properties on the Rect Transform component:
图 13.25:InputField 的文本区域 - TextMeshPro
Figure 13.25: The Text Area of the InputField - TextMeshPro
这Placeholder 和 Text 子项只是 Text - TextMeshPro 对象。您可以在第 10 章中找到有关 Text - TextMeshPro 对象的更多信息。
The Placeholder and Text children are simply Text - TextMeshPro objects. You can find more information about the Text - TextMeshPro objects in Chapter 10.
输入字段 - TextMeshPro GameObject 包含一个图像组件。如果要更改输入框的外观,请更改InputField ( TMP) pa上图像组件的源图像租。
An Input Field - TextMeshPro GameObject contains an Image component. If you want to change the appearance of the input box, change the Source Image of the Image component on the InputField (TMP) parent.
这父 InputField (TMP) 对象有一个TextMeshPro – 输入字段组件。它具有可交互 UI 对象共有的所有属性、标准 UI 输入字段的许多相同属性以及一些输入字段 - TextMeshPros 独有的属性。本节将不讨论输入字段 - TextMeshPros 与 UI 输入字段共享的属性,因为它们已在上一节中讨论过,我们将仅讨论其独有的属性。您可以在下图中看到这些属性:
The parent InputField (TMP) object has a TextMeshPro – Input Field component. It has all the properties common to the interactable UI objects, many of the same properties of the standard UI Input Field, and a few that are exclusive to Input Field - TextMeshPros. This section will not discuss the properties that Input Field - TextMeshPros share with UI Input Fields since they were discussed in the previous section, and we will only discuss those that are exclusive to it. You can see the properties in the following image:
图 13.26:TextMeshPro – 输入字段组件
Figure 13.26: The TextMeshPro – Input Field component
Text Viewport属性设置为输入文本应在哪个区域的 Rect Transform 中显示。可见。文本区域的 Rect Transform默认情况下,此属性分配给了 child。如前所述,Text Area child 有一个 Rect Mask 2D 组件,可防止文本在Text Area 的 Rect Transform 组件定义的区域之外可见。
The Text Viewport property is set to the Rect Transform of the area in which the entered text should be visible. The Rect Transform of Text Area child is assigned to this property, by default. As stated earlier, the Text Area child has a Rect Mask 2D component that stops text from becoming visible outside of the area defined by the Rect Transform component of the Text Area.
Text Component属性设置为应显示输入文本的对象的 Text Mesh Pro UGUI 组件。分配给此属性的 TextMeshPro - Text 对象将确定输入文本的字体和显示设置。默认情况下,Text 子项的 Text Mesh Pro UGUI 组件被分配给此属性。
The Text Component property is set to the Text Mesh Pro UGUI component of the object in which the entered text should display. The TextMeshPro - Text object assigned to this property will determine the font and display settings of the entered text. The Text Mesh Pro UGUI component of the Text child is assigned to this property, by default.
文本输入框组可以展开以显示较大的文本输入区域。文本输入框属性的工作方式与UI 输入的输入字段组件上的文本属性相同字段对象。用户输入的文本将被存储此处,可通过代码访问。这将存储输入的实际文本,而不是格式化的文本。例如,如果文本已格式化为显示为星号(如Pin和密码内容类型),则实际的 PIN 或密码将存储在此处,而不是字符串星号。
The Text Input Box group can be expanded to display a large text input area. The Text Input Box property works the same way as the Text property on the Input Field component of UI Input Field objects. The text entered by the user will be stored here and can be accessed by code. This will store the actual text entered and not the formatted text. For example, if the text has been formatted to appear as asterisks (as with Pin and Password Content Types), the actual pin or password will be stored here rather than a string of asterisks.
字体资源属性确定输入字段 - TextMeshPro 中显示的各种文本的字体,Point Size属性确定文本的大小。您会注意到 Placeholder 和 Text 子项在其 Text Mesh Pro UGUI 组件上也具有Font Asset和Point Size属性。更改输入字段 - TextMeshPro 父项上的Font Asset和Point Size属性也会更改子对象上的相应属性。
The Font Asset property determines the font of the various texts displayed within the Input Field - TextMeshPro, and the Point Size property determines the size of the text. You’ll note that the Placeholder and Text children also have the Font Asset and Point Size properties on their Text Mesh Pro UGUI components. Changing the Font Asset and Point Size properties on the Input Field - TextMeshPro parent will also change the corresponding properties on the child objects.
The rest of the properties in this group are the ones included within the UI Input Field.
如果选择了OnFocus - Select All属性后,当选择了输入字段 - TextMeshPro 时,字段内的所有文本都会被高亮显示。
If the OnFocus - Select All property is selected, when the Input Field - TextMeshPro is selected, all the text within the field will be highlighted.
如果选择了“Reset On DeActivation”属性,则插入符号将重置为文本前面的默认位置。
If the Reset On DeActivation property is selected, the caret will reset to the default position at the front of the text.
如果选择了“按 ESC 键恢复”属性,则按下ESC键时文本将重置为默认值。默认值为空字符串或场景启动时在文本输入框中输入的任何内容。
If the Restore on ESC Key property is selected, the text will reset back to the default when the esc key is hit. The default will be either an empty string or whatever is entered in the Text Input Box when the scene starts.
富文本属性表示接受任何富文本标签,允许富文本编辑属性允许用户在 该领域。
The Rich Text property means that any rich text tags to be accepted, and the Allow Rich Text Editing property allows the user to enter rich text tags within the field.
这输入字段 - TextMeshPro 有四个默认事件:值改变时事件、结束编辑时事件、选择时事件和取消选择时事件,如值改变时(字符串)、结束编辑时(字符串)、选择时(字符串)和取消选择时(字符串)部分所示。
The Input Field - TextMeshPro has four default events: the On Value Changed Event, the On End Edit Event, the On Select Event, and the On Deselect Event, as shown in the On Value Changed (String), On End Edit (String), On Select (String), and On Deselect (String) sections.
前两个事件,即“值改变时事件”和“结束编辑时事件”与 UI 输入字段中显示的事件相同。
The first two events, the On Value Changed Event and the On End Edit Event are the same as those presented in the UI Input Field.
第三个事件是On Select事件。每当选择输入字段 - TextMeshPro 时,都会触发此事件。第四个事件是On Deselect事件。正如您所料,每当取消选择输入字段 - TextMeshPro 时,都会触发此事件。它的工作原理类似于On End Edit事件,只是它在提交文本时不会触发。
The third event is the On Select Event. This event fires whenever the Input Field - TextMeshPro is selected. The fourth event is the On Deselect Event. As you would expect, the event fires whenever the Input Field - TextMeshPro is deselected. It works similarly to the On End Edit event, except that it does not fire when the text is submitted.
与本章讨论的其他事件一样,您可以选择不向On Select 事件和On Deselect事件传递参数、传递静态参数或传递动态参数。
As with the other events discussed in this chapter, you can choose to pass no argument, a static argument, or a dynamic argument to the On Select and On Deselect events.
现在我们已经回顾了 uGUI 的各种可交互组件,让我们看一些示例来了解使用它们。
Now that we’ve reviewed the various interactable components of the uGUI, let’s look at some examples of how to use them.
本章包含如此多的新内容,以至于我可以用本书的其余部分来向您展示示例!遗憾的是,我不能这样做,所以我将向您展示一些我希望最有用的示例。L让我们开始吧。
This chapter has so many new items in it that I could spend the rest of this book just showing you examples! Sadly, I can’t do that, so I will show you examples that I hope will be the most useful. Let’s begin.
让我们继续处理我们的场景并创建一个下拉菜单,让我们可以在猫和狗之间切换玩家角色。最终版本将如下所示:
Let’s continue working on our scene and create a dropdown menu that will allow us to swap our player character between a cat and a dog. The final version will appear as follows:
图 13.27:暂停菜单下拉菜单的最终版本
Figure 13.27: The final version of the Paused Menu dropdown
Changing our selection will then change the image of the character that appears at the top of the screen.
包含狗图像的精灵表是我从此处的免费艺术资产中修改而来的资产:
The spritesheet containing the dog image is an asset that I’ve modified from free art assets found here:
https://opengameart.org/content/cat-dog-free-sprites
https://opengameart.org/content/cat-dog-free-sprites
这与为我们提供猫精灵的资产相同。
This is the same asset that provided us with the cat sprites.
当你想在播放模式下查看 UI 下拉菜单时,你必须按P键调出暂停面板。当你只想快速检查布局时,这可能会有点烦人。你可以暂时禁用主摄像头上的ShowHidePanels.cs脚本来禁用暂停面板的自动隐藏。只需记住在你完成了!
When you want to see your UI Dropdown menu in play mode, you have to press P to bring up the Pause Panel. This can be kind of annoying when you just want to quickly check the layout. You can disable the automatic hiding of the Pause Panel momentarily by disabling the ShowHidePanels.cs script on the Main Camera. Just remember to turn it back on when you are done!
To create a UI Dropdown menu like the one shown in the previous image, complete the following steps:
图 13.28:下拉菜单的矩形变换
你的下拉菜单现在应该如下所示:
图 13.29:下拉菜单的当前状态
Figure 13.28: The Rect Transform of the Dropdown
Your Dropdown should now appear as follows:
Figure 13.29: The current state of the Dropdown
展开层次结构中的下拉菜单以查看其子项,将显示一个名为Arrow的子项。您可以调整Arrow的属性来更改其外观和一般位置。
为Arrow提供uiElements_132精灵并调整 Rect Transform,如图所示:
Expanding the Dropdown in the Hierarchy to view its children will reveal a child named Arrow. You can adjust the properties of Arrow to change its look and general position.
Give the Arrow the uiElements_132 sprite and adjust the Rect Transform, as illustrated:
图 13.30:箭头的矩形变换
Figure 13.30: The Rect Transform of the Arrow
笔记
Note
在项目视图的搜索栏中输入132可以快速找到uiElements_132图像。
Typing 132 in the search bar of the Project view is a quick way to find the uiElements_132 image.
图 13.31:标题的矩形变换
Figure 13.31: The Rect Transform of the Caption
We’ve set it to the image of the cat so that we can see whether it is displaying properly but remember that it will automatically change to the appropriate sprite based on the selection.
图 13.32:标签的矩形变换
如果您尝试调整文本,它将恢复为选项 A,因为这是由下拉列表组件驱动的。
Figure 13.32: The Rect Transform of the Label
If you try to adjust the Text, it will revert to Option A, since this is driven by the Dropdown component.
我们不会使用Scrollbar,因此我们可以保留其原样。它将显示在 Scene 视图中,但在 Play 模式下不可见,因为其Visibility在Template 的Scroll Rect 组件中设置为Auto Hide And Expand Viewport 。
We won’t use the Scrollbar, so we can leave it as it is. It will show up in the Scene view but will not be visible in Play mode, because its Visibility is set to Auto Hide And Expand Viewport in the Scroll Rect component of Template.
选择Item并将其 Rect Transform Height更改 为50。
Select Item and change its Rect Transform Height to 50.
图 13.33:项目的矩形变换和层次结构
Figure 13.33: The Rect Transform and Hierarchy of the Item
如果你玩过这个游戏,Pos Y会在Content的 Rect Transform 上从0变为其他值。这实际上是应该发生的(选项 A设置在标题后面),但当你试图布置你的Item时,这会很烦人,因为你将无法看到你的Item。
因此,播放完毕后,将Pos Y改回0,即可继续编辑。
If you played the game, the Pos Y will change on Rect Transform of Content from 0 to something else. This is actually supposed to happen (option A is being set behind the caption), but it’s annoying when you are trying to lay out your Item, because you won’t be able to see your Item.
Therefore, after playing, change Pos Y back to 0 so that you can continue editing.
在Hierarchy中右键单击Item并选择UI | Image为其添加子Image。将其重命名为Item Image并将其移动到Hierarchy中的Item Checkmark和Item Label之间。
Right-click on Item in the Hierarchy and select UI | Image to give it a child Image. Rename it Item Image and move it between Item Checkmark and Item Label in the Hierarchy.
图 13.34:项目图像的矩形变换和层次结构
Figure 13.34: The Rect Transform and Hierarchy of the Item Image
图 13.35:项目标签的矩形变换
Figure 13.35: The Rect Transform of the Item Label
图 13.36:下拉菜单的当前状态
Figure 13.36: The current state of the dropdown
图 13.37:按下“播放”按钮时下拉菜单的当前状态
Figure 13.37: The current state of the dropdown when pressing Play
我们只需要两个选项,因此选择选项 C,然后点击减号 ( - ) 按钮将其删除。现在,将文本选项 A更改为Cat,将文本选项 B 更改为Dog。
将catSprites_0拖到Cat下的精灵槽中,将dogSprites_0拖到Dog下的精灵槽中。您的 Dropdown 组件属性应显示如下:
图 13.38:Dropdown 组件的属性
如果你玩游戏,你会看到下拉菜单现在显示了适当的选项列表和标题根据您的选择更新图像和文本:
We only need two options, so select Option C and then hit the minus (-) button to delete it. Now, change the text Option A to Cat and the text Option B to Dog.
Drag catSprites_0 into the sprite slot under Cat and dogSprites_0 into the sprite slot under Dog. Your Dropdown component properties should appear, as follows:
Figure 13.38: The properties of the Dropdown component
If you play the game, you will see that the dropdown now shows the appropriate list of options and the caption image and text update based on your selection:
Figure 13.39: The final visual set up of the dropdown options
现在我们的下拉菜单看起来符合我们的要求,并且功能正常,我们可以通过代码访问玩家的选择。我们将使用玩家的选择来更新屏幕左上角的玩家角色图像。
Now that our Dropdown looks the way we want it and is functioning properly, we can access the player’s selection with code. We’ll use the player’s selection to update the player character image in the top-left corner of the screen.
要将玩家角色图像与下拉菜单中的选择交换,请完成以下步骤:
To swap the player character image with the selection from the Dropdown, complete the following steps:
使用 UnityEngine.UI;
using UnityEngine.UI;
我们将此脚本附加到 Dropdown 对象,这样我们就不必将引用它的变量公开。在命名空间声明后添加以下变量声明:
公共图像角色图像; 下拉列表 dropDown;
We’ll attach this script to the Dropdown object, so we don’t have to make the variable referencing it public. Add the following variable declarations after the namespace declarations:
public Image characterImage; Dropdown dropDown;
无效唤醒(){
dropDown = GetComponent<下拉列表>();
}void Awake(){
dropDown = GetComponent<Dropdown>();
}公共无效DropDownSelection(int选择索引){
}public void DropDownSelection(int selectionIndex){
}将以下两行添加到DropDownSelection函数中:
Debug.Log("玩家选择了" + dropDown.options[selectionIndex].text);
字符图像.sprite = dropDown.options[selectionIndex].图像;第一行将在选项列表中的指定索引处找到选项上的文本并将其打印到控制台。
第二行将在选项列表中的指定索引处找到选项上的精灵,并将角色图像上的精灵更改为该精灵。
Add the following two lines to your DropDownSelection function:
Debug.Log("player selected " + dropDown.options[selectionIndex].text);
characterImage.sprite = dropDown.options[selectionIndex].image;The first line will find the text on the option at the specified index in the options list and print it to the console.
The second line will find the sprite on the option at the specified index in the options list and change the sprite on the characterImage to that sprite.
请记住,dropDown变量不是公共的,因为我们希望将此脚本作为组件附加到Dropdown 。
Remember, the dropDown variable is not public, because we expected to attach this script as a component to the Dropdown.
图 13.40:On Value Changed(Int32)属性
Figure 13.40: The On Value Changed (Int32) property
就是这样!现在玩游戏,观察玩家角色的图像与下拉菜单中选择的图像交换:
That’s it! Now play the game and watch the player character’s image swap with the image selected from the Dropdown:
图 13.41:场景的最终版本
Figure 13.41: The final version of the scene
Figure 13.41 shows the final version of the scene of the game.
谁知道有这么多不同类型的可交互 UI 对象?拥有这些不同 UI 对象的模板非常有用。从技术上讲,它们都可以用按钮、图像和文本手动构建,但这需要很多努力,您不必担心,因为 Unity 已经为您完成了。在本章中,我们回顾了如何使用常见的 UI 元素:切换、滑块、下拉菜单和输入字段。
Who knew there were so many different types of interactable UI objects? Having templates for these different UI objects is incredibly helpful. Technically, they can all be built by hand with Buttons, Images, and Text, but that would take a lot of effort you don’t have to worry about because Unity has done it for you. In this chapter, we reviewed how to use the common UI elements: Toggle, Slider, Dropdown, and Input Field.
接下来,我们将介绍如何在UI 中使用动画!
Next, we will cover using animations within the UI!
在本部分中,您将了解与 Unity UI 系统相关的更多高级主题。您将学习如何为 UI 元素设置动画以及在 UI 中显示粒子。您将学习如何创建出现在游戏世界中的 UI。最后,您将获得注意事项概述,以确保创建优化的 UI。
In this part, you’ll learn about more advanced topics related to the Unity UI system. You’ll learn how to animate UI elements as well as display particles within the UI. You’ll learn how to create UI that appears within the world of your game. Lastly, you’ll get an overview of considerations to make sure you create an optimized UI.
本部分包含以下章节:
This part has the following chapters:
由于我们已经讨论了如何为按钮创建动画过渡,在本章中,我们将更彻底地了解动画过渡,并讨论如何以更一般的意义为 UI 元素创建动画。
Since we have already discussed how to create animation transitions for buttons, in this chapter, we’ll take a look at animation transitions more thoroughly and discuss how to create animations for UI elements in a more general sense.
本章假设您对 Unity 的动画系统有基本的了解,因此不会详细描述各种菜单的名称以及动画窗口和动画器窗口的布局。动画剪辑和动画器将简要描述,重点介绍它们与 UI 的关系及其实现,这将在本章末尾的示例中讨论。
This chapter assumes that you have a basic understanding of Unity’s Animation System and will not go into detail describing the names of the various menus and layout of the Animation Window and Animator Window. Animation Clips and Animators will be described briefly, with a focus on how they relate to UI and their implementation, which will be discussed in the examples at the end of the chapter.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
尽管我假设您对 Unity 的动画系统有基本的了解,但我还是想强调动画剪辑和动画器之间的区别。
Even though I am assuming that you have a basic understanding of Unity’s Animation System, I do want to emphasize the difference between an Animation Clip and the Animator.
在 Unity 中为项目创建动画时,首先要使用动画剪辑。动画剪辑应代表单个不同的动作或运动。例如,如果您有一个菜单执行两个单独的操作,即弹跳和缩放,则应将每个操作都设置为单独的动画剪辑。虽然您可以在一个动画剪辑中发生多件事,但除非它们总是同时发生,否则不要将多个动作放在一个剪辑中,这一点非常重要。
When creating animations for items in Unity, you start with Animation Clips. An Animation Clip should represent a single distinct action or motion. So, for example, if you had a menu that performed two separate actions, bouncing and zooming, you’d make each of those actions a separate Animation Clip. Although you can have multiple things happen in a single Animation Clip, it is very important not to put multiple actions in a single clip unless they are always going to happen at the same time.
每个游戏对象都可以有多个动画剪辑。动画器决定了所有这些动画如何链接在一起。因此,游戏对象的动画器将包含其所有动画剪辑。
Every GameObject can have multiple Animation Clips. The Animator determines how all of these Animations link together. So, a GameObject’s Animator will have all of its Animation Clips within it.
我想做出这种区分,因为在过去,我见过一些项目,其中的史诗动画剪辑包含多个动作,而这些动作应该分解为更简单的动作。
I wanted to make this distinction because in the past, I have seen projects with epic Animation Clips containing multiple actions that should have been broken down into more simple motions.
笔记
Note
与前面的章节一样,本节中展示的所有示例都可以在代码包中提供的 Unity 项目中找到。它们可以在标记为Chapter14 的场景中找到。
As with previous chapters, all of the examples shown in this section can be found within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter14.
您可以在此处找到本章的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2014
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2014
Unity 的伟大之处动画系统的优点是您可以为 UI 的几乎任何属性设置动画。要创建动画剪辑,只需打开动画窗口(窗口|动画或Ctrl + 6),然后选择要设置动画的 UI 元素,然后选择创建:
The great thing about the Unity Animation System is that you can animate nearly any property of the UI. To create an Animation Clip, simply open the Animation Window (Window | Animation or Ctrl + 6), and with the UI element you want to animate selected, select Create:
图 14.1:动画窗口
Figure 14.1: The Animation Window
完成后,系统将提示您保存动画剪辑。
Once you do so, you’ll be prompted to save the Animation Clip.
创建动画剪辑后,您可以通过单击“添加属性”将任何属性添加到剪辑的时间轴:
After creating the Animation Clip, you can then add any property to the clip’s timeline by clicking on Add Property:
图 14.2:SliderExampleAnimation 时间轴
Figure 14.2: The SliderExampleAnimation timeline
Doing so will bring up every component of the object, as well as a list of all of its children:
图 14.3:添加动画属性
Figure 14.3: Adding an Animation property
您还可以查看每个子项的组件和子项:
You can also view the components and children of each child:
图 14.4:扩展动画组件
Figure 14.4: Expanding Animation components
然后,您可以查看这些子组件及其子组件。您可以继续此方式,直到您用尽了嵌套在所选 GameObject 下的 GameObject 列表。
Then, you can view the components and children of those children. You can continue in this manner until you have exhausted the list of GameObjects that are nested under the selected GameObject.
如果你展开 GameObject 的组件或其子组件之一,你会看到该组件的所有可动画属性的列表。如下面的屏幕截图所示,Slider组件上的几乎每个属性都可以通过这种方式进行动画处理:
If you expand a component of a GameObject or one of its children, you will then note a list of all properties of that component that can be animated. As you will notice in the following screenshot, almost every property on the Slider component can be animated in this way:
图 14.5:Slider 组件及其动画属性
Figure 14.5: The Slider component and its Animatable properties
只有具有以下数据类型的属性才可以使用动画系统进行动画处理:
Only properties that have the following data types can be animated with the Animation System:
选择加号会将属性连同两个关键帧一起添加到动画时间轴。然后您可以更改各个关键帧处每个属性的值。
Selecting the plus sign will add the property to the Animation timeline along with two keyframes. You can then change the values of each property at the various keyframes.
关键帧是动画中的重要或关键帧(因此得名)。它表示动画中过渡的起点或终点。
A keyframe is an important or key (hence the name) frame within an animation. It represents the start or end point of a transition within an animation.
图 14.6:SliderExampleAnimation 第一个关键帧
Figure 14.6: SliderExampleAnimation first keyframe
在上面的截图中,红色方框内的值表示该属性在特定帧的值。布尔值用复选框表示,浮点值用数字表示。每种类型都可以通过选择直接编辑。
In the preceding screenshot, the values encased in the red boxes represent the value of the property at the particular frame. Boolean values are represented by checkboxes, and float values are represented by numbers. Each type can be edited directly by selecting them.
Unity 将在关键帧之间填充值,以便它们在曲线上变化(或插值)。您可以通过选择“曲线”选项卡来查看插值曲线。
Unity will fill in the values between the keyframes so that they change (or interpolate) on a curve. You can view the interpolation curve by selecting the Curves tab.
您可以通过播放动画或拖动播放头来观察所有帧中发生的变化。
You can watch the changes occur throughout all frames by playing the animation or scrubbing the playhead.
播放头是指示当前正在显示哪一帧的标记。“拖动播放头”是指在时间线上拖动播放头以查看各个帧的变化。
The playhead is the marker that indicates what frame is currently being displayed. “Scrubbing the playhead” means dragging the playhead across the timeline to view changes over individual frames.
图 14.7:曲线版本的时间线
Figure 14.7: The Curves version of the timeline
布尔属性在线性曲线上进行插值。所有其他属性(因为它们是浮点数的组合)都沿缓入缓出曲线进行插值。您可以通过调整手柄来调整这些通过右键单击关键帧,可以查看关键帧处的切线。
Boolean properties are interpolated on a linear curve. All other properties (since they are a combination of floats) are interpolated along an ease-in-out curve. You can adjust these by adjusting the handles of the tangents at the keyframes by right-clicking on the keyframe.
通常,这些缓入缓出曲线会导致您的 UI 出现弹性,而调整插值曲线可以消除这种弹性。例如,在两点之间为对象设置动画可能会使对象暂时越过目标点,这是由于默认插值曲线的缓入缓出特性。
Often, these ease-in-out curves will cause your UI to appear bouncy, and adjusting the interpolation curve can remove that bounce. For example, animating an object between two points may make the object go past the destination point momentarily due to the ease-in-out nature of the default interpolation curve.
我最喜欢的东西之一关于动画剪辑,可以向时间线上的帧添加动画事件。动画事件允许您调用现有的函数在指定帧的 GameObject 上。它们由时间线上的白色旗帜表示,将鼠标悬停在它们上面将显示动画事件调用的函数的名称。
One of my favorite things about Animation Clips is the ability to add Animation Events to frames on the timeline. Animation Events allow you to call functions that exist on the GameObject at specified frames. They are represented by white flags above the timeline, and hovering over them will show the name of the function called by the Animation Event.
动画事件只能调用动画剪辑所附加到的游戏对象上某处的函数。该函数可以是公共的或私有的,也可以有一个参数。参数可以是以下类型:
An Animation Event can only call a function that exists somewhere on the GameObject the Animation Clip is attached to. The function can be public or private and can also have a parameter. The parameter can be of the following types:
您可以通过右键单击要放置动画事件的帧上方的区域并单击“添加动画事件” ,将动画事件添加到动画剪辑的时间轴,或者您可以单击添加事件按钮。选择添加事件按钮将在播放头当前所在的位置添加动画事件。
You can add an Animation Event to the Animation Clip’s timeline by right-clicking on the area above the frame in which you wish to place the Animation Event and clicking on Add Animation Event, or you can click the Add Event button. Selecting the Add Event button will add the Animation Event wherever the playhead currently rests.
图 14.8:动画事件
Figure 14.8: Animation Events
您可以通过选择白旗并单击“删除”来删除动画事件,您可以通过单击并拖动它来移动它。
You can delete the Animation Event by selecting the white flag and clicking on Delete, and you can move it by clicking on it and dragging it.
的外观Animation Event 的 Inspector 取决于动画剪辑是否附加到 GameObject 以及 GameObject 当前是否被选中。两种外观如下图所示:
The appearance of the Animation Event’s Inspector depends on whether the Animation Clip is attached to a GameObject and whether the GameObject is currently selected. The two appearances are shown in the following screenshot:
图 14.9:动画事件检查器
Figure 14.9: The Inspector of the Animation Event
如果动画剪辑附加到游戏对象并且选择了游戏对象,则会出现一个下拉菜单,其中列出了所有可用的函数。如果所选函数有参数,则将提供与该参数有关的选项(请查看前面的屏幕截图)。否则,必须输入函数的名称和要传递的参数。手动输入。
If the Animation Clip is attached to a GameObject and the GameObject is selected, a dropdown menu will appear with a list of all available functions. If the selected function has a parameter, then options concerning the parameter will be made available (take a look at the preceding screenshot). Otherwise, the name of the function and the parameters to pass will have to be entered manually.
如果您在启用滑块动画示例游戏对象的情况下播放第 14 章场景,您将看到一个正在播放事件的动画。
If you play the Chapter14 scene with the Slider Animation Example GameObject enabled, you will see an animation with events playing out.
Now that we’ve looked at Animation Clips, let’s look at Animation Controllers.
每当动画为对象创建剪辑时,会自动为其创建动画器(如果尚不存在)。动画器组件也会自动添加到对象中。当我在上一节中在Slider游戏对象上创建SliderExampleAnimation动画剪辑时,会创建一个名为Slider 的动画器,并将动画器组件附加到Slider游戏对象上:
Whenever an Animation Clip is created for an object, an Animator is automatically created for it (if one does not already exist). An Animator component is also automatically added to the object. When I created the SliderExampleAnimation Animation Clip on the Slider GameObject in the preceding section, an Animator named Slider was created and the Animator component was attached to the Slider GameObject:
图 14.10:滑块动画器的检查器
Figure 14.10: The Slider Animator’s Inspector
需要一个动画器来播放动画剪辑,因为它决定何时播放动画剪辑。
An Animator is needed to play Animation Clips because it determines when Animation Clips are played.
Animator 是一种决策树又称为状态机。它包含一系列状态。状态本质上是某一时刻的状态。状态机的当前状态将表示此刻正在发生的事情。例如,如果有一个状态机描述我的操作和行为,那么我的当前状态就是在键盘上打字。我的状态机将具有我最终可以转换到的其他状态,例如在某些条件下睡觉或为临近最后期限而哭泣得到满足。
An Animator is a type of decision tree known as a state machine. It holds a collection of states. States are essentially statuses at a moment in time. The current state of a state machine would be a representation of what is happening at this moment. So, for example, if there were a state machine describing my actions and behaviors, my current state would be typing on the keyboard. My state machine would have other states that I could eventually transition to, such as sleeping or crying about approaching deadlines if certain conditions are met.
Animator 中的状态由矩形称为节点。状态通过箭头线表示的转换连接。这些转换在预定时间或一组条件满足后发生。当前状态将有一个蓝色的动画状态栏,告诉您已完成的状态百分比。如果当前状态正在等待转换发生,则此状态栏可能会循环或停止在完整位置,直到满足转换条件:
States in the Animator are represented by rectangles called nodes. States are connected by transitions that are represented by arrow lines. These transitions occur after either a predetermined time or a set of conditions have been met. The current state will have a blue, animated status bar on it telling you the percentage of the state that has been completed. If the current state is waiting for a transition to occur, this status bar may loop or stop in the full position until the conditions of the transition are met:
图 14.11:滑块动画器
Figure 14.11: The Slider Animator
动画器窗口的右下角显示当前动画器控制器的名称及其文件夹位置。当您有许多不同的动画器时,这会非常有用,因为它会告诉您正在使用哪个动画器。
The bottom-right corner of the Animator Window displays the name of the current Animator controller as well as its folder location. This can be very helpful when you have many different Animators, as it tells you which Animator you are working with.
状态可以是空的,或者表示动画剪辑。如果某个状态表示动画剪辑,则它将在其Inspector中将动画剪辑设置为其Motion属性,如以下屏幕截图所示:
States can be empty or represent Animation Clips. If a state represents an Animation Clip, it will have an Animation Clip set to its Motion property in its Inspector, as in the following screenshot:
图 14.12:动画状态的运动属性
Figure 14.12: The Motion property of an Animation State
大多数状态将以灰色显示,但其他颜色的状态将代表特殊状态。每个动画器都将有一个入口节点(绿色)、一个出口节点(红色)和一个任意状态节点(蓝色)。您添加到动画器的第一个状态将被分配默认图层状态节点(橙色)。您可以随时更改默认图层状态的状态。请注意,动画器图层将在后面的部分中讨论。
Most states will be colored gray, but those colored otherwise will represent special states. Every Animator will have an Entry node (green), an Exit node (red), and an Any State node (blue) within it. The first state you add to an Animator will be assigned the Default Layer State node (orange). You can change the state of the Default Layer State at any time. Note that Animator Layers are discussed in a later section.
入口节点和出口节点本质上充当状态机之间的门。您可以在状态机内设置状态机,这些门分别决定进入和退出状态机后会发生什么。因此,入口节点表示状态机启动的实例,而出口节点表示状态机停止的实例。
The Entry and Exit nodes essentially work as gates between state machines. You can have state machines within state machines, and these gates decide what happens after the state machine is entered and exited, respectively. So, the Entry node represents the instance the state machine starts, and the Exit node represents the instance it stops.
Entry节点始终会过渡到 Default Layer State,并且您无法定义过渡的条件,因此过渡将始终自动且立即发生。因此,您可以将 Default Layer State 视为状态机启动时将发生的第一个状态。
The Entry node always transitions to the Default Layer State, and you cannot define the conditions of the transition, so the transition will always happen automatically and instantly. Therefore, you can think of the Default Layer State as the first state that will occur when the state machine begins.
任何状态节点都是一个包罗万象的状态。当您想要发生转换时,可以使用此状态,无论当前状态。您只能从任何状态节点转移。继续使用描述我的行为的状态机示例,我将从任何状态转移到因截止日期临近而哭泣的状态,因为无论我现在正在做什么,如果满足“截止日期在 24 小时内”的条件,我都会泪流满面。
The Any State node is an all-encompassing state. You use this state when you want a transition to happen, regardless of the current state. You can only transition away from the Any State node. Continuing with the example of a state machine that describes my behavior, I would have a transition from Any State to the state of crying about approaching deadlines, because no matter what I am currently doing, I could burst into tears if the condition “deadline is within 24 hours” is met.
如前所述,动画器将保持当前状态,直到经过指定的时间或满足一组条件。选择过渡箭头将显示过渡条件:
As stated before, the Animator will stay in the current state until a specified amount of time has passed or a set of conditions have been met. Selecting a transition arrow will display the conditions of transition:
图 14.13:动画的扩展属性
Figure 14.13: Expanded properties of an Animation
从上面的截图需要指定的时间和条件。转换也不是瞬间完成的,需要0.25秒才能完成。
The transition from the preceding screenshot requires both a specified amount of time and a condition. The transition also isn’t instantaneous and takes 0.25 s to complete.
发生转换所必须满足的条件由动画器的参数设置,可以在动画器窗口的左上角找到和创建:
The Conditions that must be met for a transition to occur are set by the Animator’s Parameters, which can be found and created in the top-left corner of the Animator Window:
图 14.14:添加动画器参数
Figure 14.14: Adding an Animator Parameter
这些参数的值可以从脚本中设置。参数有四种类型:Float、Int、Bool和Trigger。前三种以其值类型命名,但Trigger不太明显。Trigger 是一个 Bool 参数,在转换使用后会立即重置为False。触发器参数有助于创建洪水门类型动画必须停止并等待才能进入下一个状态的操作。在状态和转换形成循环的情况下,这些操作比布尔参数更受欢迎,因为布尔参数必须在状态循环回来之前手动重置。
The values of these parameters can be set from scripts. There are four types of Parameters: Float, Int, Bool, and Trigger. The first three are named for their value type, but a Trigger is a little less obvious. A Trigger is a Bool Parameter that instantly resets itself to False after it is used by a transition. Trigger Parameters are helpful for creating flood-gate-type actions where the animation has to stop and wait before it can proceed to the next state. These are preferred over Bool Parameters in instances where the states and transitions form a loop, because a Bool Parameter would have to be manually reset before the state looped back around.
查看参数列表时,可以通过右侧的值判断它们的类型。浮点型参数有小数,整型参数有整数,布尔型参数有方形复选框,触发器参数有圆形单选按钮。
When you look at the list of Parameters, you can tell which type they are by the value on the right. Float Parameters have decimal numbers, Int Parameters have integers, Bool Parameters have square checkboxes, and Trigger Parameters have circular radio buttons.
由于 Animators 是状态机,因此它们除了动画之外还能完成很多其他任务。Animators 还可用于跟踪复杂的游戏逻辑。例如,我为一款三消 RPG 游戏创建了以下状态机,以跟踪游戏中当前发生的情况。使用它来跟踪游戏的当前状态,我可以根据游戏中发生的情况限制玩家可以做的事情。
Since Animators are state machines, they can be used to accomplish much more than animations. Animators can be used to keep track of complex game logic. For example, I created the following state machine for a match-three RPG to keep track of what was currently happening in the game. Using it to keep track of the current state of the game allowed me to restrict what the player could do based on what was happening in the game.
例如,如果敌方角色正在攻击,玩家将不会与棋盘上的棋子互动:
For example, if the enemy character was attacking, the player would not interact with the pieces on the board:
图 14.15:用于逻辑的动画状态机示例
Figure 14.15: Example of a Animation State Machine used for logic
现在我们已经回顾了Animator Controller 的属性,让我们看看它的一些用法塞斯。
Now that we’ve reviewed the properties of the Animator Controller, let’s look at some of its uses.
在第 9 章中,我们了解了按钮动画过渡并为按钮创建了一个简单的动画。我们让按钮组件自动为我们生成动画,但从未查看过动画或用它做任何事情。现在我们已经讨论了动画,让我们看看保存在Assets/Animations中的播放按钮的动画:
In Chapter 9, we took a look at Button Animation transitions and created a simple animation for a Button. We let the Button component automatically generate the Animator for us, but never looked at the Animator or did anything with it. Now that we’ve discussed Animators, let’s look at the Animator of the Play Button saved in Assets/Animations:
图 14.16:按钮的动画器
Figure 14.16: The Animator of a Button
从上面的截图中可以看出,为我们自动生成的 Animator 并不特别复杂,其设置也是不言自明的。它包含以下五个动画剪辑的状态:Normal、Highlighted、Pressed、Selected和Disabled 。所有动画剪辑都从Any State过渡。此外,还有五个触发参数:正常、高亮、按下、选中、禁用。
As you can see from the preceding screenshot, the Animator that was automatically generated for us isn’t particularly complicated and its setup is self-explanatory. It contains states to hold the following five Animation Clips: Normal, Highlighted, Pressed, Selected, and Disabled. All of the Animation Clips transition from Any State. Also, there are five Trigger Parameters: Normal, Highlighted, Pressed, Selected, and Disabled.
为允许过渡动画的任何 UI 元素自动生成动画器将导致完全相同的设置。尽管此动画器已为您预设,但您可以随意调整它合身。
Automatically generating an Animator for any of the UI elements that allow for transition animations will result in the exact same setup. Even though this Animator is preset for you, you are free to adjust it however you see fit.
使用 Animator 时,如果你如果某个状态可以转换到多个节点,则只能发生一次转换。例如,在下面的屏幕截图中,ChooseAState状态一次只能转换到其他状态之一,即使满足所有转换条件也是如此;无论您使用哪种类型的参数,情况都是如此:
When using the Animator, if you have a state with transitions to multiple nodes, only one transition can occur. For example, in the following screenshot, the ChooseAState state can only transition to one of the other states at once, even if the transition conditions for all are met; this is true regardless of the type of Parameter that you use:
图 14.17:第 14 章场景中的分叉动画示例
Figure 14.17: A forking animation example in the Chapter14 scene
如果你想要多个动画一次触发,您可以使用动画图层。以下图层设置将同时运行所有三个状态:
If you want multiple animations to trigger at once, you can use Animation Layers. The following layer setup will have all three states running simultaneously:
图 14.18:第 14 章场景中的动画图层示例
Figure 14.18: An Animation Layers Example in the Chapter14 scene
我发现最常见的需求是当你有一个由多个精灵表组成的对象,并且你想同时触发多个精灵表动画,而把它们都放在同一个动画剪辑上是没有意义的。例如,我曾经开发过一款游戏,其中的 2D 角色有多个可互换的部分,每个部分都有自己的精灵表动画。有必要让每个部分的空闲动画同时开始。由于这些部分可以互换,因此可以实现多个部分的组合,而制作所有不同的空闲动画组合是没有意义的。给每个可能的部分一个自己的动画器也是没有意义的。所以,我为每个身体部位制作了一个图层,并能够让各个精灵动画同时播放时间。
I’ve found that the most common need for something like this is when you have an object made of multiple sprite sheets and you want multiple sprite sheet animations to trigger at the same time and putting them all on the same Animation Clip doesn’t make sense. For example, I’ve worked on a game where a 2D character had multiple interchangeable parts, and each part had its own sprite sheet animation. It was necessary to have the idle animation for each part start all at the same time. Since the parts could be swapped out, there were multiple combinations of parts that could be achieved, and it would not have made sense to make all the different possible idle animation combinations. It also wouldn’t have made sense to give each possible part its own Animator. So, I made a layer for each body part and was able to have the individual sprite animations all play at the same time.
您可以使用Animator类的SetFloat()、SetInteger()、SetBool()、SetTrigger()和ResetTrigger()函数通过脚本设置 Animator 参数的值。您可以参考Animator 参数变量通过在Animator 中分配给它们的字符串名称来表示。
You can set the values of the Animator Parameters via scripts using the SetFloat(), SetInteger(), SetBool(), SetTrigger(), and ResetTrigger() functions of the Animator class. You reference the Animator Parameter variables by the string names assigned to them within the Animator.
要设置动画参数,首先要获取定义参数的动画器;可以使用公共动画器变量或使用GetComponent<Animator>()执行此操作。然后,在动画器上调用必要的函数。
To set the Animation Parameters, you first get the Animator on which the Parameters were defined; you can do this with either a public Animator variable or using GetComponent<Animator>(). Then, you call the necessary function on the Animator.
让我们看一个设置以下参数的示例:
Let’s look at an example that would set the following Parameters:
图 14.19:不同类型的动画参数
Figure 14.19: Different types of Animation Parameters
以下脚本将设置上一个屏幕截图中定义的动画参数:
The following script would set the Animator Parameters defined in the previous screenshot:
使用System.Collections;
使用 System.Collections.Generic;
使用 UnityEngine;
公共类第 14 章示例:MonoBehaviour
{
动画师 theAnimator;
无效唤醒()
{
theAnimator = GetComponent<Animator>();
}
公共无效设置动画参数()
{
theAnimator.SetFloat("Float参数", 1.0f);
theAnimator.SetInteger("Int参数", 1);
theAnimator.SetBool("Bool参数", true);
theAnimator.SetTrigger("TriggerParameter"); // 设置为 true
theAnimator.ResetTrigger("TriggerParameter"); // 设置为 false
}
公共无效示例函数()
{
Debug.Log("动画事件没有发送参数");
}
公共无效示例参数函数(int 值)
{
Debug.Log("动画事件发送以下值:" + value);
}
}using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Chapter14Examples : MonoBehaviour
{
Animator theAnimator;
void Awake()
{
theAnimator = GetComponent<Animator>();
}
public void SetAnimatorParameters()
{
theAnimator.SetFloat("FloatParameter", 1.0f);
theAnimator.SetInteger("IntParameter", 1);
theAnimator.SetBool("BoolParameter", true);
theAnimator.SetTrigger("TriggerParameter"); // sets to true
theAnimator.ResetTrigger("TriggerParameter"); // sets to false
}
public void ExampleFunction()
{
Debug.Log("The Animation Event is not sending an argument");
}
public void ExampleParameterFunction(int value)
{
Debug.Log("The Animation Event sends the following value: " + value);
}
} 使用的好处触发器的优点在于,您通常不需要重置它,因为它会在转换使用它时立即重置。但是,如果您设置了触发器并且转换从未达到,则需要使用ResetTrig重置它ger()。
The benefit of using a Trigger is that you usually don’t have to reset it as it instantly resets the moment a transition uses it. However, if you set a Trigger and the transition is never reached, you will need to reset it using ResetTrigger().
如果你想编写在状态中的特定点触发的代码,你可以使用一种称为 State 的独特脚本类机器行为。状态机器行为可以添加到您在 Animator 中创建的任何状态节点。我指定您创建,因为您无法将它们添加到入口节点、出口节点或任何状态节点。
If you want to write code that fires at specific points within a state, you can use a unique class of scripts known as State Machine Behaviours. State Machine Behaviours can be added to any state node you create within the Animator. I specify you create because you cannot add them to the Entry node, Exit node, or Any State node.
您可以通过选择一个状态并单击“添加行为”来创建新的状态机行为:
You can create a new State Machine Behaviour by selecting a state and clicking on Add Behaviour:
图 14.20:如何向动画状态添加行为
Figure 14.20: How to add a Behaviour to an Animation State
All new State Machine Behaviours created in this way are saved in the Assets folder.
当您打开脚本时,它将自动填充以下代码:
When you open the script, it will be automatically populated with the following code:
使用System.Collections;
使用 System.Collections.Generic;
使用 UnityEngine;
公共类 ChooseAStateBehaviour:StateMachineBehaviour
{
// 当转换开始并且状态机开始评估此状态时,将调用 OnStateEnter
//覆盖
公共覆盖void OnStateEnter(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
{
// 在此实现状态进入逻辑
}
// 在 OnStateEnter 和 OnStateExit 回调之间,每个更新帧都会调用 OnStateUpdate
//覆盖
公共覆盖void OnStateUpdate(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
{
// 在此实现状态更新逻辑
}
// 当转换结束且状态机完成评估此状态时,调用 OnStateExit
//覆盖
公共覆盖void OnStateExit(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
{
// 在此实现状态退出逻辑
}
// OnStateMove 在 Animator.OnAnimatorMove() 之后立即调用
//覆盖
公共覆盖void OnStateMove(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
{
// 实现处理和影响根运动的代码
}
// OnStateIK 在 Animator.OnAnimatorIK() 之后立即调用
//覆盖
公共覆盖void OnStateIK(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
{
// 实现设置动画 IK(逆运动学)的代码
}
}using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ChooseAStateBehaviour : StateMachineBehaviour
{
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
//override
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Implement state enter logic here
}
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
//override
public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Implement state update logic here
}
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
//override
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Implement state exit logic here
}
// OnStateMove is called right after Animator.OnAnimatorMove()
//override
public override void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Implement code that processes and affects root motion
}
// OnStateIK is called right after Animator.OnAnimatorIK()
//override
public override void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Implement code that sets up animation IK (inverse kinematics)
}
} 请注意,此类派生自StateMachineBehaviour而不是MonoBehaviour,就像我们附加到 GameObjects 的脚本一样。
Note that this class is derived from StateMachineBehaviour rather than MonoBehaviour, like the scripts we attach to GameObjects.
脚本中预先为您编写了一些函数,并附有使用方法的说明。正如Awake()、Start()和Update()是 MonoBehaviour 的预定义函数一样,OnStateEnter()、OnStateUpdate()、OnStateExit()、OnStateIK()和OnStateMove()也是在特定时间调用的预定义函数。您可以删除不想使用的任何函数。您还可以编写其他该脚本中的函数并不局限于这些预定义的函数。
There are a few functions prewritten in the script for you, along with descriptions of how to use them. Just as Awake(), Start(), and Update() are predefined functions for MonoBehaviour, OnStateEnter(), OnStateUpdate(), OnStateExit(), OnStateIK(), and OnStateMove() are predefined functions that call at specific times. You can delete whichever functions you don’t want to use. You can also write other functions within this script, and they are not restricted to these predefined ones.
这些函数可以做任何你想做的事情,甚至可以设置你的动画器的动画器参数。
These functions can do whatever you want them to do, even set the Animator Parameters of your Animator.
我发现状态机行为非常有用,因为我广泛使用状态机来控制游戏逻辑。在本节前面,我向您展示了我为三消 RPG 创建的状态机。我使用多个状态机行为来让我的其他脚本知道状态何时发生变化,在特定时间调用其他脚本中的函数等等。
I find State Machine Behaviours to be incredibly helpful because I use state machines extensively to control the logic of my games. Earlier, in this section, I showed you a state machine I created for a match-three RPG. I used multiple State Machine Behaviours to let my other scripts know when the states had changed, call functions from other scripts at specified times, and so on.
现在我们已经回顾了如何使用动画剪辑和动画控制器,让我们看一些如何在我们的中实现它们项目。
Now that we’ve reviewed using Animation clips and Animator Controllers, let’s look at some examples of how to implement them in our project.
示例
Examples
本章的主要重点是提供如何创建常见 UI 动画和效果的示例,让我们开始吧。在这些示例中,我将向您展示一个基本的可重复使用的动画,用于淡入淡出 UI 面板,以及一个更复杂的依赖于玩家兴趣的战利品盒动画反应。
The main focus of this chapter is to provide examples of how to create common UI animations and effects, so let’s get to it. In these examples, I will show you a basic re-usable animation for fading UI Panels in and out, as well as a more complex animation of a loot box dependent on player interaction.
With our first example, we will continue to work on our main scene.
目前,我们有一个暂停面板和库存面板,当按下P或I键时会立即出现。这不是很有趣,所以让我们添加一些动画,让面板通过淡入淡出和缩放动画弹出和隐藏。
Currently, we have a Pause Panel and Inventory Panel that instantly appear when the P or I key is pressed. That’s not terribly interesting, so let’s add some animations to have the Panels pop in and out with fade and scale animations.
每次进入新场景时,都必须展开所有父级才能看到其子级,这可能会变得相当繁琐。打开所有父级的快捷方式是选择层次结构中的所有内容,然后按键盘上的右箭头键。如果有多个嵌套,您可以多次执行此操作。左箭头键将折叠父级。
It can get rather tedious having to expand all of the parents to see their children every time you go to a new scene. A shortcut to open all parents is to select everything in the Hierarchy and then press the right arrow key on the keyboard. You can do this multiple times if you have multiple nestings. The left arrow key will collapse the parents.
我们设置这些动画及其功能的工作流程是创建动画剪辑、设置动画器,然后编写代码在适当的时间设置动画器的参数。为了使这些步骤更容易理解,我将它们分成几个部分。第一部分涵盖设置动画剪辑和动画器所涉及的所有步骤,第二部分涵盖编写动画所需的设置代码。
Our workflow to set up these animations and their functionality will be to create Animation Clips, set up our Animator, and then write code that sets the Animator’s parameters at the appropriate times. To make the steps easier to digest, I’ve broken them into sections. The first section covers all of the steps involved with setting up the Animation Clips and Animator and the second section covers the sets involved with writing code.
To create a pop in and out animation on Pause Panel and Inventory Panel, perform the following steps:
图 14.21:添加比例属性
Figure 14.21: Adding the Scale property
图 14.22:调整动画的长度
Figure 14.22: Adjusting the length of the animation
图 14.23:调整比例属性
如果播放动画,您会注意到暂停面板正在快速缩小。由于父/子关系会使子级随其父级缩放,因此我们不必分别为所有暂停面板 子级的缩放设置动画。
Figure 14.23: Adjusting the Scale properties
If you play the animation, you’ll note that Pause Panel is quickly scaling in. Since parent/child relationships scale children with their parents, we don’t have to animate the scale of all the Pause Panel children separately.
图 14.24:添加 Alpha 属性
Figure 14.24: Adding the Alpha property
图 14.25:FadeAndScale 动画剪辑的时间轴
Figure 14.25: The Timeline of the FadeAndScale Animation Clip
选择暂停面板后,打开动画窗口。您应该看到类似以下内容:
图 14.26:暂停面板的动画器
您将看到一个名为FadeAndScale的状态。此状态使用FadeAndScale动画剪辑作为其运动,您可以在其 Inspector 中查看。
With Pause Panel selected, open the Animator Window. You should see something similar to the following:
Figure 14.26: The Animator of Pause Panel
You will see a state named FadeAndScale. This state uses the FadeAndScale Animation Clip as its Motion, which you can view in its Inspector.
图 14.27:创建空状态
Figure 14.27: Creating an Empty State
This will add a gray-colored state named New State to the Animator.
图 14.28:将状态设置为图层默认值
现在它将通过转换连接到Entry。FadeAndScale现在也不再是 Layer 默认状态,并且不会有任何转换与其连接:
图 14.29:将空状态设置为图层默认值
Figure 14.28: Setting a state as the Layer Default
It will now be connected to Entry via a transition. FadeAndScale will also now no longer be the Layer Default State and will not have any transitions connected to it:
Figure 14.29: Empty State set as the Layer Default
We used an empty state as our Layer Default State because we want the Panel to do nothing, while it waits for us to tell it to start animating.
图 14.30:重新排列各州
Figure 14.30: Rearranging the States
Deselect the Loop Time property to disable looping on the animation:
图 14.31:取消选择循环时间
Figure 14.31: Deselecting Loop Time
图 14.32:复制状态
Figure 14.32: Duplicating a State
图 14.33:设置动画以反向播放
Figure 14.33: Setting an animation to play in reverse
图 14.34:动画器参数
我们在这里使用触发器参数,因为我们希望这些值在使用后立即重置。这样,我们就可以创建一个动画循环,而不必编写重置参数值的代码。
Figure 14.34: The Animator Parameters
We are using Trigger Parameters here because we want these values to instantly reset after we’ve used them. That way, we can create an animation cycle without having to write code that resets the parameter values.
图 14.35:进行转换
按以下方式创建状态之间的转换:
图 14.36:最终的状态转换布局
此过渡流程将允许面板从无动画过渡到FadeAndScale动画剪辑,然后过渡到反向的FadeAndScale动画剪辑,并在两者之间来回过渡。
Figure 14.35: Making transitions
Create transitions between the states in the following manner:
Figure 14.36: The final state transition layout
This transition flow will allow the Panel to transition from no animation to the FadeAndScale Animation Clip, then to the reversed FadeAndScale Animation Clip, and back and forth between the two.
图 14.37:过渡属性
Figure 14.37: The transition properties
图 14.38:如何设置暂停面板的过渡
Figure 14.38: How to set up the transitions of the Pause Panel
图 14.39:画布组的属性
现在,当您玩游戏时,“暂停面板”将不会在开始时出现。
Figure 14.39: The properties of the Canvas Group
Now when you play the game, the Pause Panel will not appear at the start.
图 14.40:播放空状态
Figure 14.40: Playing the Empty State
You’ll see the progress bar on the current state of Pause Panel running. To force the transitions, click on the circles next to the appropriate Trigger parameters.
由于我们将在两个不同的对象上使用我们创建的动画器,因此最好重命名它。将名称从Pause Panel更改为PopUpPanels。现在,将其拖到Inventory Panel上。
Since we will be using the Animator we created on two different objects, it is a good idea to rename it. Change the name from Pause Panel to PopUpPanels. Now, drag it onto the Inventory Panel.
现在我们已经完成了两个面板的动画设置,我们可以开始编写触发按下P和I键调出P时的动画安妮尔斯。
Now that we are done setting up our animations for our two Panels, we can begin writing code that will trigger the animations when the P and I keys are hit to bring up the Panels.
我们有一个名为ShowHidePanels.cs的脚本附加到主摄像头,当按下P和I键时,它会调出暂停面板和库存面板。遗憾的是,它不再起作用,因为动画现在取代了我们在其中设置的画布组。我们可以重复使用逻辑,但需要做一些工作才能让我们的面板再次弹出。
We have a script named ShowHidePanels.cs attached to the Main Camera that would bring the Pause Panel and Inventory Panel up when the P and I keys, respectively, are pressed. Sadly, it no longer functions, since the animations now supersede the properties of the Canvas Groups we set within it. We can reuse the logic but will have to do a bit of work to get our Panels popping up again.
我们对ShowHidePanels.cs所做的更改将导致前一章场景中的面板停止显示。如果您计划访问前一章场景,请保存此脚本的第二个副本,以便以后访问。
The changes that we will make to ShowHidePanels.cs will cause the Panels in preceding chapter scenes to stop appearing. If you plan on accessing the previous chapter scenes, save a secondary copy of this script as it is now so that you can access it later.
要使用代码触发暂停面板和库存面板上的动画,请完成以下步骤:
To trigger the animations on the Pause Panel and Inventory Panel with code, complete the following steps:
无效开始()
{
// 启动时初始化面板
切换面板(库存面板,inventoryUp);
切换面板(pausePanel,pauseUp);
}
无效更新()
{
// 处理库存面板切换
如果(输入.GetKeyDown(KeyCode.I)&&!pauseUp)
{
库存上升 = !库存上升;
切换面板(库存面板,inventoryUp);
}
// 处理暂停面板切换
if (Input.GetButtonDown("暂停"))
{
暂停 = !暂停;
切换面板(pausePanel,pauseUp);
Time.timeScale = pauseUp ? 0 : 1; // 通过设置时间尺度暂停/取消暂停游戏
}
}void Start()
{
// Initialize Panels on Start
TogglePanel(inventoryPanel, inventoryUp);
TogglePanel(pausePanel, pauseUp);
}
void Update()
{
// Handle inventory Panel toggle
if (Input.GetKeyDown(KeyCode.I) && !pauseUp)
{
inventoryUp = !inventoryUp;
TogglePanel(inventoryPanel, inventoryUp);
}
// Handle pause Panel toggle
if (Input.GetButtonDown("Pause"))
{
pauseUp = !pauseUp;
TogglePanel(pausePanel, pauseUp);
Time.timeScale = pauseUp ? 0 : 1; // Pause/unpause game by setting time scale
}
}公共动画师inventoryPanelAnim; 公共动画师pausePanelAnim;
public Animator inventoryPanelAnim; public Animator pausePanelAnim;
公共无效FadePanel(动画动画,bool show)
{
如果(显示)
{
动画.设置触发器(“淡入”);
}
别的
{
动画.设置触发(“淡出”);
}
}public void FadePanel(Animator anim, bool show)
{
if (show)
{
anim.SetTrigger("FadeIn");
}
else
{
anim.SetTrigger("FadeOut");
}
}无效更新()
{
// 库存面板
如果(输入.GetKeyDown(KeyCode.I)&&!pauseUp)
{
库存上升 = !库存上升;
//切换面板(inventoryPanel,inventoryUp);
FadePanel(inventoryPanelAnimator,inventoryUp);
}
// 暂停面板
if (Input.GetButtonDown("暂停"))
{
暂停 = !暂停;
//切换面板(pausePanel,pauseUp);
FadePanel(pausePanelAnimator,pauseUp);
时间.timeScale = Convert.ToInt32(pauseUp);
}
}这就是我们对代码所要做的全部工作。
void Update()
{
// inventory Panel
if (Input.GetKeyDown(KeyCode.I) && !pauseUp)
{
inventoryUp = !inventoryUp;
//TogglePanel(inventoryPanel, inventoryUp);
FadePanel(inventoryPanelAnimator, inventoryUp);
}
// pause Panel
if (Input.GetButtonDown("Pause"))
{
pauseUp = !pauseUp;
//TogglePanel(pausePanel, pauseUp);
FadePanel(pausePanelAnimator, pauseUp);
Time.timeScale = Convert.ToInt32(pauseUp);
}
}That’s all we have to do to the code.
图 14.41:显示隐藏面板组件的属性
Figure 14.41: The properties of the Show Hide Panels component
时间.timeScale = Convert.ToInt32(pauseUp);
这行代码用于有效暂停游戏。但是,这意味着它暂停了我们的暂停面板弹出动画。别担心,您仍然可以使用这个简单的暂停代码并在游戏暂停时运行动画。您所要做的就是告诉暂停面板上的动画器,当时间刻度设置为0时它仍然可以运行。您可以通过将动画器组件上的更新模式从正常更改为未缩放时间来实现这一点:
Time.timeScale = Convert.ToInt32(pauseUp);
That line of code was used to effectively pause the game. However, that means it’s pausing our Pause Panel pop-up animation. Don’t worry, you can still use this simple pause code and run animations when the game is paused. All you have to do is tell the Animator on the Pause Panel that it can still function when the time scale is set to 0. You do this by changing the Update Mode on the Animator component from Normal to Unscaled Time:
图 14.42:将更新模式设置为非标度时间
Figure 14.42: Setting Update Mode to Unscaled Time
玩游戏,你应该流畅地动画暂停和库存面板。现在我们可以继续进行更复杂的即时通讯。
Play the game, and you should have smoothly animating Pause and Inventory Panels. We can now move on to a more complex animation.
在这个例子中,我们将使用一个新场景。我们将创建的动画有点复杂——一个箱子会飞进场景,然后等待玩家打开它。一旦玩家打开它,宝箱就会动画打开,粒子系统会弹出。然后,三个收藏品会按顺序飞出。每个收藏品都会有自己的闪亮动画开始播放。
For this example, we’ll work with a new scene. The animation we will create is a bit complicated—a chest will fly into the scene and then wait for the player to open it. Once the player opens it, the chest will animate open with a particle system that pops in front of it. Then, three collectibles will fly out in sequence. Each collectible will have its own shiny animation that begins to play.
下图是一个故事板,展示了动画播放的几个关键帧:
The following figure is a storyboard of sorts that shows a few keyframes of the animation playing out:
图 14.43:此示例动画的最终版本
Figure 14.43: The final version of the animation from this example
在本章中,我们将介绍除粒子系统之外的所有内容,粒子系统将在下一章中介绍。
In this chapter, we will cover all items except for the Particle System, which we will cover in the next chapter.
笔记
Note
箱子精灵表取自https://bayat.itch.io/platform-game-assets,物品精灵表取自https://opengameart.org/content/shining-coin-shining-health-shining-power-up-sprite-sheets。
The chest sprite sheet was obtained from https://bayat.itch.io/platform-game-assets and the item sprite sheets were obtained from https://opengameart.org/content/shining-coin-shining-health-shining-power-up-sprite-sheets.
这个例子有很多内容使用它。构建起来并不特别复杂,但提供从头开始构建它的步骤需要太多步骤。这将超出本书的范围;但此时,希望您可以查看已经构建好的场景并了解它是如何实现的。因此,我们将从一个包文件开始这个示例,该文件包含放置在场景中的所有项目、一些已创建的动画以及包含的所有新精灵表。
This example has a lot going on with it. It is not particularly complicated to build out, but providing the steps to build it entirely from scratch would require too many steps. That would go beyond the scope of this book; but at this point in the book, you can hopefully look at a scene that’s already been built out and understand how it was achieved. Therefore, we will start this example with a package file that has all the items placed in the scene, some of the animations already created, and all the new sprite sheets included.
开始之前,导入第 14 章- 示例 - LootBox - Start.unitypackage 资产包。
Before you begin, import the Chapter 14 - Examples - LootBox - Start.unitypackage asset package.
如果您想查看完整的示例,请查看标有第 14 章- 示例 - LootBox - End.unitypackage的包。
If you’d like to view the completed example, view the package labeled Chapter 14 - Examples - LootBox - End.unitypackage.
笔记
Note
Unity Layers 不会保存在 Unity 资源包中。示例将介绍如何创建名为UI Particles 的Layer并让摄像机忽略或包含该 Layer,但这不会显示在提供的包中。
Unity Layers do not save in Unity asset packages. The example will describe creating a Layer named UI Particles and having the cameras ignore or include the Layer, but this is not displayed in the provided package.
为了举例为了更容易理解,我将步骤分为三个不同的部分。为了完成此示例,我们将执行以下功能:
To make the example easier to absorb, I have broken the steps into three distinct sections. To complete this example, we will perform the following functions:
让我们从为场景中的每个对象创建动画开始本节。要创建所有为此场景的动画,请完成以下步骤:
Let’s start this section by creating the animations for each of the objects within the scene. To create all the animations for this scene, complete the following steps:
图 14.44:包的场景布局
导入包后,您还会注意到Assets文件夹中提供了一些动画剪辑和控制器。
Figure 14.44: The scene layout of the package
After importing the package, you will also note that there are a few animation clips and controllers provided in the Assets folder.
图 14.45: ChestFlyingIn 动画
Figure 14.45: The ChestFlyingIn animation
图 14.46:移动箱子并记录属性
如果你拖动播放头,你会看到胸部从左向右移动。
Figure 14.46: Moving the chest and recording the properties
If you scrub the playhead, you will see the chest move from left to right.
图 14.47:选择曲线菜单
Figure 14.47: Selecting the Curves menu
The green line represents the y property of the anchored position. Select the Anchored Postion.y property to focus on it.
图 14.48:调整曲线的锚点
现在,当你播放动画时,你会看到胸部沿着弧线路径移动。
Figure 14.48: Adjusting the anchors of the curve
Now, when you play the animation, you will see the chest move in an arcing path.
图 14.49:ChestFlyingIn 动画第一帧的属性
Figure 14.49: The properties of the first frame of the ChestFlyingIn animation
图 14.50:胸部动画师
Figure 14.50: The Animator of the Chest
图 14.51:创建新剪辑
将新的动画剪辑命名为ChestOpening.anim,并将其保存在Assets/Animations/LootBox/Clips文件夹中。
Figure 14.51: Creating a new clip
Name the new Animation Clip ChestOpening.anim and save it in the Assets/Animations/LootBox/Clips folder.
图 14.52:胸部精灵表动画
Figure 14.52: The Chest sprite sheet animation
图 14.53:启用采样率菜单项
Figure 14.53: Enabling the Sample Rate menu item
图 14.54:更改采样率
Figure 14.54: Changing the Sample Rate
让我们为Chest Open Canvas中的其他对象添加动画。将Coin Animator 拖到Coin GameObject的检查器中,将Heart Animator 拖到Heart GameObject的检查器中,将PowerUp Animator 拖到PowerUp GameObject 的检查器中。这些动画器中的每一个都可以在Assets/Animations/Loot Box/Controllers文件夹中找到。现在,您可以通过选择 GameObjects 并在动画窗口中按下播放来预览所有物品从箱子中弹出并闪闪发光的动画(在游戏视图中播放尚不会显示正在播放的动画)。
Let’s give the other objects in Chest Open Canvas their animations. Drag the Coin Animator to the Inspector of the Coin GameObject, the Heart Animator to the Inspector of the Heart GameObject, and the PowerUp Animator to the PowerUp GameObject’s Inspector. Each of these Animators can be found in the Assets/Animations/Loot Box/Controllers folder. You can now preview the animations of all the items popping out of the chests and shining by selecting the GameObjects and pressing play in the Animation window (Play in the Game view will not yet show the animations playing).
现在您应该只会在场景中看到“开始”按钮。
You should now only see the Start Button in the scene.
动画现在完全为每个对象设置动画。我们仍然需要完成胸部动画控制器的工作,并为各种动画添加更多逻辑,但我们已经完成了动画剪辑现在。
The animations are now completely set up for each of the objects. We still need to finish working on the Animator Controller for the Chest and add some more logic for the various Animators, but we are done with the Animation Clips for now.
The next thing to do is set up our state machine and write the code that will make the various animations play.
To hook up the various animations and have them play at the correct time, complete the following steps:
图 14.55:动画的状态机
状态机如图所示上面的截图演示了打开宝箱的动画和交互的事件顺序。当游戏等待玩家按下按钮继续播放动画时,将播放标记为“等待玩家”的状态。其他动画将根据定时事件自动播放。
Figure 14.55: The State Machine for the Animation
The state machine shown in the preceding screenshot demonstrates the sequence of events for the animations and interactions of the chest opening. The states labeled Waiting On Player will play when the game is waiting for the player to press a button to proceed with the animation. The other animations will automatically play based on timed events.
图 14.56:各种转换的触发器
Figure 14.56: The triggers for the various transitions
笔记
Note
我强烈建议你将此图像添加为书签或打印屏幕截图在执行此示例时,您可以查看ChestOpeningStateMachine动画器的动画。将此作为流程图,您可以在执行此示例时轻松参考,这将使完成的步骤更容易遵循。
I highly recommend that you bookmark this image or print out a screenshot of your ChestOpeningStateMachine Animator while working through this example. Having this as a flow chart that you can easily reference while working on this example will make the steps being completed a lot easier to follow.
图 14.57:胸部动画器
Figure 14.57: The Chest Animator
仅限动画师包含动画状态,但尚未表明它们是如何连接的。我们需要创建过渡并设置动画参数。重新排列状态并将过渡添加到 Animator,使其显示如下:
The Animator only contains the animation states but does not yet indicate how they are all connected. We will need to create transitions and set up the Animation Parameters. Rearrange the states and add transitions to the Animator so it appears as follows:
图 14.58:胸部动画器已更新
Figure 14.58: The Chest Animator updated
图 14.59:胸部动画器的触发器
Figure 14.59: The Triggers of the Chest Animator
// 不同类型的参数
公共枚举参数类型
{
浮点参数,
int参数,
boolParam,
触发参数
}
// 动画参数的属性
[系统.可序列化]
公共类参数属性
{
public string paramterString; // 什么字符串设置它?
public string whichState; // 调用它的状态的名称,null=未被状态机调用
public TypesOfParameters paramType; // 它是什么类型的 Animator Parameter?
public float floatValue; // 需要浮点值,如果是浮点数
public int intValue; // 需要 int 值,如果是 int
public bool boolValue; // 需要 Bool 值,如果是 bool
}
// 列出所有可动画的对象及其参数
[系统.可序列化]
公共类 AnimatorProperties
{
public string name; // 因此名称将出现在检查器中,而不是“元素 0、元素 1 等”
public Animator theAnimator; // 动画师
public List<ParameterProperties> animatorParameters; // 其参数属性
}您会注意到,上述代码包含以下三个部分:枚举的TypesOfParameters、ParameterProperties类和AnimatorProperties类。首先,让我们看一下枚举的TypesOfParameters。这是可在 Animator 中使用的 Animator 参数类型的列表。枚举列表是一种自定义类型,其中包含一组用名称表示的常量。使用枚举列表的好处是列表在 Inspector 中显示为下拉菜单。现在,让我们看一下ParameterProperties类。场景中动画的每个对象都有一组与其 Animator 参数相关的属性,我们需要跟踪这些属性。类可以成为将数据集分组在一起的有效工具。因此,我使用类来对参数的名称、将设置参数的状态、参数的类型以及其值(如果是浮点、整数或布尔参数)进行分组。请注意,参数类型是使用枚举列表TypesOfParameters定义的。这样做是因为每个动画器参数都有一组有限且特定的参数。现在,让我们看看AnimatorProperties子类。对于场景中的每个对象,我们需要跟踪其名称、动画器及其所有参数以及设置它们的条件。请注意,参数列表及其属性由ParameterProperties类定义。使用 Unity 的一大好处是能够分配和在检查器中查看公共变量。但是,当您在类中创建类时,除非您将[System.Serializable]置于类上方,否则公共变量在检查器中不可见。这为子类提供了Serializable属性,并允许其公共变量在检查器中可见。
// The different types of parameters
public enum TypesOfParameters
{
floatParam,
intParam,
boolParam,
triggerParam
}
// Properties of animation parameters
[System.Serializable]
public class ParameterProperties
{
public string parameterString; // What string sets it?
public string whichState; // Name of the state it's called from, null=not called by the state machine
public TypesOfParameters parameterType; // What type of Animator Parameter is it?
public float floatValue; // Float value required, if float
public int intValue; // Int value required, if int
public bool boolValue; // Bool value required, if bool
}
// Make a list of all animatable objects and their parameters
[System.Serializable]
public class AnimatorProperties
{
public string name; // So the name will appear in the inspector rather than "Element 0, Element 1, etc"
public Animator theAnimator; // The animator
public List<ParameterProperties> animatorParameters; // Its parameter properties
}You’ll note that the preceding code has the following three parts: the TypesOfParameters enumerated, the ParameterProperties class, and the AnimatorProperties class. First, let’s look at the TypesOfParameters enumerated. This is a list of the types of Animator Parameters that can be used within an Animator. An enumerated list is a custom type that contains a set of constants that are represented with names. A benefit of using an enumerated list is that the list appears as a dropdown menu within the Inspector. Now, let’s look at the ParameterProperties class. Each object that is animated within the scene has a set of properties related to its Animator Parameters that we need to keep track of. A class can be an effective tool for grouping sets of data together. Therefore, I used a class to group the name of the parameter, which state the parameter will be set in, what type of parameter it is, and its value if it is a float, integer, or Boolean parameter. Note that the type of parameter is defined using the enumerated list, TypesOfParameters. This was done because there is a finite and specific set of parameters available for each animator parameter. Now, let’s look at the AnimatorProperties subclass. For each object in the scene, we will need to keep track of its name, its animator, and all of its parameters along with the conditions in which they are set. Note that the list of parameters and their properties is defined by the ParameterProperties class. A big benefit of working with Unity is the ability to assign and view public variables in the Inspector. However, when you create a class within a class, the public variables are not visible within the Inspector unless you place [System.Serializable] above the class. This gives the subclass the Serializable attribute and allows its public variables to be visible in the Inspector.
笔记
Note
我想指出的是,此代码允许动画器具有Float、Int和Bool参数,尽管我们在任何动画器中使用的唯一参数是触发器。我以这种方式编写它是为了使其更通用,以便您将来可以将此代码重复用于其他动画。
I would like to point out that this code allows for the Animators to have Float, Int, and Bool parameters, even though the only parameters we use in any of our Animators are Triggers. I wrote it in this way to make it work more universally so that you can reuse this code for other animations in the future.
public List<AnimatorProperties> animatedItems; //此状态机控制的所有动画项目
public List<AnimatorProperties> animatedItems; //all the animated items controlled by this state machine
图 14.60:胸部动画控制组件
Figure 14.60: The Chest Anim Controls component
图 14.61:胸部动画控件的动画器属性
请记住animatedItems变量是AnimatorProperties的列表。因此,元素 0(以及所有其他元素)包含AnimatorProperties类中分组的所有项目。
Figure 14.61: The AnimatorProperties of the Chest Anim Controls
Remember that the animatedItems variable was a list of AnimatorProperties. So, Element 0 (and all the other Elements for that matter) contains all the items that were grouped in the AnimatorProperties class.
图 14.62:胸部打开画布属性
Figure 14.62: The Chest Open Canvas properties
图 14.63:ParameterProperties 和 TypesOfParameters
请记住,animatorParameters变量是ParameterProperties的列表。因此,这两个元素包含ParameterProperties类中分组的所有项目。此外,在ParameterProperties类中,parameterType是一个TypesOfParameters变量。TypesOfParameters是一个枚举列表,因此该类型的任何变量都将作为下拉列表出现菜单中包含定义列表中出现的选项。
Figure 14.63: The ParameterProperties and TypesOfParameters
Remember that the animatorParameters variable was a list of ParameterProperties. So, the two Elements contain all the items that were grouped in the ParameterProperties class. Additionally, within the ParameterProperties class, parameterType was a TypesOfParameters variable. TypesOfParameters was an enumerated list, so any variable of that type will appear as a dropdown menu with the options that appeared within the defined list.
图 14.64:胸部打开画布属性
Figure 14.64: The Chest Open Canvas properties
Since each is a Trigger Animator Parameter, we do not have to worry about the values for Float Value, Int Value, or Bool Value.
图 14.65:箱子属性
Figure 14.65: The Chest properties
图 14.66:硬币属性
Figure 14.66: The Coin properties
图 14.67:心脏属性
Figure 14.67: The Heart properties
图 14.68:PowerUp 属性
Figure 14.68: The PowerUp properties
图 14.69:按钮属性
Figure 14.69: The Button properties
Animator theStateMachine; //状态机动画组件
Animator theStateMachine; //the state machine animator component
无效唤醒()
{
theStateMachine = GetComponent<Animator>(); // 获取状态机
}void Awake()
{
theStateMachine = GetComponent<Animator>(); // Get the state machine
}// 功能:检查是否有动画需要设置其参数
// 从进入状态调用
公共无效检查参数集()
{
// 循环遍历所有对象
foreach(animatorProperties 在 animatedItems 中的 animatorProp)
{
// 循环遍历其参数集
foreach(animatorProp.animatorParameters 中的 ParameterProperties 参数)
{
// 查找当前状态下调用的
如果(theStateMachine.GetCurrentAnimatorStateInfo(0)。IsName(参数。哪个状态))
{
// 判断参数类型
// 浮点类型
如果(参数.parameterType == TypesOfParameters.floatParam)
{
animatorProp.theAnimator.SetFloat(参数.parameterString,参数.floatValue);
}
// 整数类型
否则,如果(参数.parameterType == TypesOfParameters.intParam)
{
animatorProp.theAnimator.SetInteger(参数.parameterString,参数.intValue);
}
// 布尔类型
否则,如果(参数.parameterType == TypesOfParameters.boolParam)
{
animatorProp.theAnimator.SetBool(参数.parameterString,参数.boolValue);
}
// 触发器类型
别的
{
animatorProp.theAnimator.SetTrigger(参数.parameterString);
}
}
}
}
}CheckForParameterSet函数将确定指定的 Animator 是否需要在状态机的当前状态下设置参数。但是,此函数不目前在任何地方调用。我们希望这个函数在任何时候被调用状态机中的状态开始。我们可以使用状态机行为来实现这一点。打开ChestOpeningStateMachine动画器并选择Canvas Fading In状态。在状态的检查器中,单击添加行为按钮。选择新脚本,输入ChestStateMachineBehaviour,然后单击创建并添加按钮。名为ChestStateMachineBehaviour的新脚本将添加到Assets文件夹中。将其移动到Assets/Scripts/LootBox文件夹并打开它。
// Functionality: Check if any of the animations need their parameters set
// Called from enter state
public void CheckForParameterSet()
{
// Loop through all of the objects
foreach (AnimatorProperties animatorProp in animatedItems)
{
// Loop through its set of parameters
foreach (ParameterProperties parameter in animatorProp.animatorParameters)
{
// Find the ones called on the current state
if (theStateMachine.GetCurrentAnimatorStateInfo(0).IsName(parameter.whichState))
{
// Determine parameter type
// Float types
if (parameter.parameterType == TypesOfParameters.floatParam)
{
animatorProp.theAnimator.SetFloat(parameter.parameterString, parameter.floatValue);
}
// Int types
else if (parameter.parameterType == TypesOfParameters.intParam)
{
animatorProp.theAnimator.SetInteger(parameter.parameterString, parameter.intValue);
}
// Bool type
else if (parameter.parameterType == TypesOfParameters.boolParam)
{
animatorProp.theAnimator.SetBool(parameter.parameterString, parameter.boolValue);
}
// Trigger type
else
{
animatorProp.theAnimator.SetTrigger(parameter.parameterString);
}
}
}
}
}The CheckForParameterSet function will determine whether a specified Animator needs a parameter set at the current state of the State Machine. However, this function is not currently called anywhere. We want this function to be called whenever a state in the State machine starts. We can accomplish this with a State Machine Behaviour. Open the ChestOpeningStateMachine Animator and select the Canvas Fading In state. In the state’s Inspector, click on the Add Behaviour button. Select New Script, enter ChestStateMachineBehaviour, and click on the Create and Add button. A new script named ChestStateMachineBehaviour will be added to the Assets folder. Move it to the Assets/Scripts/LootBox folder and open it.
ChestAnim控制控制器脚本;
公共无效唤醒()
{
// 获取保存状态机逻辑的脚本
theControllerScript = FindObjectOfType<ChestAnimControls>();
}
// 当转换开始并且状态机开始评估此状态时,将调用 OnStateEnter
公共覆盖void OnStateEnter(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
{
检查参数集();
}ChestAnimControls theControllerScript;
public void Awake()
{
// Get the script that holds the state machine logic
theControllerScript = FindObjectOfType<ChestAnimControls>();
}
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
theControllerScript.CheckForParameterSet();
}// 由玩家互动调用(等待玩家)
公共无效PlayerInputTrigger(字符串触发器字符串)
{
状态机.设置触发器(触发器字符串);
}此函数将触发由作为参数发送的字符串指定的状态机的触发参数。
// Called by player interactions (Waiting On Player)
public void PlayerInputTrigger(string triggerString)
{
theStateMachine.SetTrigger(triggerString);
}This function will trigger the State Machine’s trigger parameter specified by the string sent as an argument.
图 14.70:按钮的 On Click() 事件
Figure 14.70: The On Click() event of the Button
使用 UnityEngine.UI;
using UnityEngine.UI;
文本按钮文本;
动画师chestAnimController;
无效唤醒()
{
按钮文本 = transform.GetComponentInChildren<文本>();
chestAnimController = Camera.main.GetComponent<Animator>();
}
公共无效打开或关闭()
{
Debug.Log(“点击”);
if (buttonText.text == “打开”)
{
chestAnimController.SetTrigger(“打开箱子”);
设置文本(“关闭”);
}
别的
{
chestAnimController.SetTrigger(“关闭胸部”);
设置文本(“打开”);
}
}
公共无效设置文本(字符串设置文本到)
{
按钮文本.文本 = 设置文本到;
}OpenOrClose()函数将由按钮的On Click ()事件调用。Chest Open Canvas上的按钮将用于打开和关闭箱子。它将根据按钮上写的当前文本设置适当的触发器,并使用SetText()函数将其文本更改为“打开”或“关闭” 。
Text buttonText;
Animator chestAnimController;
void Awake()
{
buttonText = transform.GetComponentInChildren<Text>();
chestAnimController = Camera.main.GetComponent<Animator>();
}
public void OpenOrClose()
{
Debug.Log("click");
if (buttonText.text == "Open")
{
chestAnimController.SetTrigger("OpenChest");
SetText("Close");
}
else
{
chestAnimController.SetTrigger("CloseChest");
SetText("Open");
}
}
public void SetText(string setTextTo)
{
buttonText.text = setTextTo;
}The OpenOrClose() function will be called by the button›s On Click () Event. The Button on the Chest Open Canvas will be used to open and close the chest. It will set the appropriate trigger based on the current text written on the button and will change its text to "Open" or "Close" with the SetText() function.
图 14.71:按钮的 On Click() 属性
Figure 14.71: The On Click() property of the Button
动画师chestAnimController;
无效唤醒()
{
chestAnimController = Camera.main.GetComponent<Animator>();
}
// 在动画的最后一帧上调用动画事件
公共无效ProceedStateMachine()
{
chestAnimController.SetTrigger(“动画完成”);
}Animator chestAnimController;
void Awake()
{
chestAnimController = Camera.main.GetComponent<Animator>();
}
// Call as an animation event on the last frame of animations
public void ProceedStateMachine()
{
chestAnimController.SetTrigger("AnimationComplete");
}此触发器用于允许每个单独的动画在开始下一个状态之前完全播放。为了确保在整个动画播放完毕之前不设置动画触发器,我们将使用动画事件在必要动画中的适当时间调用ProceedStateMachine()函数。
当您使用动画事件时,要调用的函数必须位于与动画附加到同一对象的脚本上。我们希望在Chest Open Canvas、Chest、Coin、Heart、PowerUp和Chest Open Canvas上的按钮上的动画结束时调用该函数。因此,将AnimationControls脚本作为组件添加到每个动画中。
This trigger is used to allow each of the individual animations to fully play before the next state is started. To make sure that the animation trigger isn’t set until the entire animation has played, we’ll use Animation Events to call the ProceedStateMachine() function at the appropriate time within the necessary animations.
When you use an Animation Event, the function you want to call must be on a script attached to the same object as the animation. We want the function to be called at the end of animations on Chest Open Canvas, Chest, Coin, Heart, PowerUp, and the Button on Chest Open Canvas. Therefore, add the AnimationControls script as a component to each of them.
图 14.72:将 ProceedStateMachine 事件添加到动画
Figure 14.72: Adding the ProceedStateMachine Event to the animation
// 当转换开始并且状态机开始评估此状态时,将调用 OnStateEnter
覆盖公共无效OnStateEnter(Animator动画师,AnimatorStateInfo stateInfo,int layerIndex)
{
动画师.ResetTrigger(“动画完成”);
}// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.ResetTrigger("AnimationComplete");
}现在玩游戏时动画序列应该可以正确触发,除了粒子系统之外的所有内容,这将在下一章中介绍。
Playing the game now should have the animation sequence firing correctly, with everything but the particle system, which will be covered in the next chapter.
在 Unity 中,为 UI 元素制作动画与为任何其他 2D 对象制作动画并无太大区别。因此,本章简要概述了动画。本章还提供了使用状态机和动画事件创建复杂动画的工作流程示例。我们可以用动画做很多事情,本章中的示例向您展示了在为 UI 制作动画时可以使用的许多技术。希望这些示例能为您提供足够的技术变化,以便您能够确定将来如何制作自己的动画。
Animating UI elements is not significantly different from animating any other 2D object in Unity. Therefore, this chapter offered a brief overview of animation. This chapter also offered an example of the workflow for creating complex animations utilizing a State Machine and Animation Events. There’s a lot we can do with animations, and the examples in this chapter showed you many of the techniques you can use while animating UI. Hopefully, these examples will provide you with enough variation of technique that you can determine how to make your own animations in the future.
在下一章中,我们将讨论如何在UI前渲染粒子效果。
In the next chapter, we will discuss how to render particle effects in front of the UI.
粒子效果是一种有趣且有吸引力的方式,可以为您的游戏增添“活力”。 Unity 引擎中的粒子系统为您提供了制作各种有趣效果(如火花、烟雾、火焰等)所需的工具。本章将讨论如何在UI 中使用粒子效果。
Particle effects are a fun and attractive way to add “juice” to your game. The Particle system within the Unity Engine provides you with the tools needed to make all sorts of interesting effects like sparkles, smoke, fire, and so on. This chapter will discuss how you can use particle effects within your UI.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
本书是关于 UI 的,而不是粒子效果。由于粒子效果的复杂性,我不会介绍所有涉及的设置以及使用粒子效果的各种复杂性。我将引导您完成创建单个粒子效果的步骤,但不会进一步深入介绍创建粒子效果的过程。
This book is about UI, not particle effects. Due to the complex nature of particle effects, I will not be covering all the settings involved and the various intricacies of using particle effects. I will walk you through the steps to create a single particle effect but will not dive further into the process of creating particle effects.
您可以在此处找到本章节的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2015
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2015
在 UI 中使用粒子是一种热门话题。似乎几乎每个带有战利品箱的手机游戏都使用粒子,但没有标准化的方法来实现它们。尝试在 UI 中使用粒子的问题在于,粒子在渲染模式设置为屏幕空间 -覆盖的画布上的 UI 后面渲染。
Using particles in UI is a hot topic. It seems like nearly every mobile game with loot boxes uses particles, but there is no standardized way to implement them. The problem with trying to use particles in the UI is that particles render behind UI on Canvases that have their Render Mode set to Screen Space - Overlay.
图 15.1:画布后面的粒子渲染
Figure 15.1: Particles rendering behind a Canvas
上面的屏幕截图显示了我们工作示例中的两个 UI Canvas 和一个粒子系统(白点)。粉色背景位于Background Canvas上,使用Screen Space - Camera进行渲染。圆形仪表和带有猫的 Panel 位于HUD Canvas上,使用Screen Space - Overlay进行渲染。粒子在 Background Canvas 前面和HUD Canvas后面进行渲染。但是,我希望粒子也将显示在HUD 画布前面。
The preceding screenshot shows two UI Canvases from our working examples and a particle system (the white dots). The pink background is on the Background Canvas, which renders with Screen Space - Camera. The circular meter and the Panel with the cat are on the HUD Canvas, which renders with Screen Space - Overlay. The particles are rendering in front of the Background Canvas and behind the HUD Canvas. However, I want the particles to be displayed also in front of the HUD Canvas.
这个问题有几种解决方案。我首选的两个解决方案是以下任一方案:
There are a few solutions to this problem. My two preferred solutions are either of the following:
这两种方法各有优缺点。第一种方法是最简单的。它允许您在仅对场景进行一两处修改的情况下在 UI 前查看粒子。但是,使用屏幕空间 - 相机作为 UI 的渲染模式可能对您的项目不切实际。如果您在游戏中编辑相机的属性,UI 的属性将被更改。此外,在设置好一切后更改画布的渲染模式可能会导致您的 UI 停止按您最初预期的方式显示。
There are benefits and downfalls to both methods. The first method is by far the easiest. It allows you to view particles in front of your UI with only one or two modifications to your scene. However, using Screen Space - Camera for your UI’s Render Mode may not be practical for your project. If you edit the properties of the Camera in your game, the properties of the UI will be changed. Additionally, changing the Render Mode of your Canvas after you have already set everything up can cause your UI to stop displaying the way you initially intended.
第二种方法实现起来并不复杂,但确实需要比第一种方法更多的工作。它的主要优点是您可以在使用屏幕空间 - 覆盖渲染的画布上在 UI 前面渲染粒子。除了需要更多工作来设置之外,它的主要缺点是,您可能必须对两个摄像头要渲染的内容做出一些复杂的决定,并且可能会稍微影响性能。本章的示例部分讨论了涵盖此方法的示例。
The second method isn’t terribly complicated to implement but does require more work than the first. Its main benefit is that you can render particles in front of UI on Canvases that render with Screen Space - Overlay. Its main downfall, other than needing more work to set up, is that you may have to make some complicated decisions about what your two cameras are going to render, and may slightly affect performance. An example covering this method is discussed in the Examples section of this chapter.
这个问题还有其他解决方案,每个解决方案都比下一个更复杂(或更昂贵),您选择做什么取决于您的项目。有些项目完全放弃粒子,而是使用 After Effects 等软件将其粒子预渲染为精灵表。有些项目使用 Asset Store 中提供的资源,而其他项目则完全使用脚本和着色器。虽然我无法预见我提出的第二个解决方案不适用于您的项目的任何原因,但您的项目肯定有可能存在我没有考虑到的极端情况。如果是这样的话,希望您能够以最小的努力修改我的解决方案以完成您的项目。
There are other solutions to this problem, each more complicated (or costly) than the next, and what you choose to do depends on your project. Some projects forego particles entirely and pre-render their particles as sprite sheets using software, such as After Effects. Some projects use assets available in the Asset Store, and others handle everything entirely with scripts and shaders. Although I can’t foresee any reason why the second solution I proposed will not work for your project, it’s certainly possible that your project has a fringe case I am not considering. Hopefully, if that is the case, you will be able to modify my solution with minimal effort to work on your project.
我给你的最佳建议是尽早决定是否要在 UI 中使用粒子。如果你知道要使用它们,请提前规划你的 UI 布局和相机设置。此外,如果你想使用第一种方法,那就去做吧。
My best advice to you would be to decide early whether you will use particles in your UI. If you know you are going to use them, plan ahead with your UI layouts and camera setups. Also, if you want to use the first method, do it.
Unity 没有实现此标准方法,这实在太糟糕了。我认为有一天会有一个预构建的 UI Particle 对象,使该过程既简单又高效。
It’s really too bad that there is no standard method for this implemented by Unity. I assume that one day there will be a pre-built UI Particle object that will make the process both simple and performant.
对于本章中的示例,我们将在第 14 章中创建的动画中添加战利品箱打开时发生的粒子效果。
For the examples in this chapter, we will add to the animations created in Chapter 14 to add a particle effect that occurs when the loot box opens.
让我们创建一个粒子系统当箱子打开时会弹出。如本章前面所述,我在 UI 前面显示粒子的两种首选方式是使用屏幕空间 - 相机作为画布渲染模式或使用渲染纹理。由于第二种选择更复杂,因此值得举一个例子。您会注意到我们的画布都将其渲染模式设置为屏幕空间 - 覆盖,因此使用渲染纹理是当前项目设置方式的最佳方法。
Let’s create a particle system that will pop when the chest opens up. As stated earlier in this chapter, my two preferred ways of displaying particles in front of UI are to either use Screen Space - Camera as the Canvas Render Mode or to use a Render Texture. Since the second option is more complicated, it merits an example. You’ll notice that our Canvases all have their Render Modes set to Screen Space - Overlay, so using a Render Texture is the best method for the way the project is currently set up.
我们将创建一个粒子系统,通过第二个摄像机将其渲染为纹理,然后将该纹理显示在UI中的原始图像上。
We will create a particle system that is rendered to a texture via a second camera and then have that texture displayed on a Raw Image within the UI.
要创建粒子系统,请完成以下步骤。我们将在下一节中将其显示在 UI 中:
To create a particle system, complete the following steps. We will work on displaying it in the UI in the next section:
图 15.2:创建粒子系统作为 UI 粒子相机的子项
Figure 15.2: Creating a Particle System as a child of UI Particles Camera
笔记
Note
由于本书不是关于粒子系统而是关于 UI,因此我们不会花时间介绍粒子系统的每个属性。幸运的是,大多数属性都是不言自明的,摆弄各种属性可以让您了解它们可以做什么。因此,我不会介绍粒子系统的每个属性,而是只提供必要属性的屏幕截图。
Since this book isn’t about Particle Systems but about UI, we will not spend time going over every property of Particle Systems. Luckily, most are somewhat self-explanatory, and fiddling with the various properties lets you see what they can do. Therefore, rather than going through each property of the Particle System, I will simply provide screenshots of the necessary properties.
图 15.3:将 StarsMaterial 材质添加到 Renderer Material 属性
Figure 15.3: Adding StarsMaterial material to the Renderer Material property
图 15.4:更改变换旋转
Figure 15.4: Changing the Transform Rotation
图 15.5:更新一些粒子系统设置
Figure 15.5: Updating some of the Particle System settings
图 15.6:更新起始大小和起始旋转
Figure 15.6: Updating Start Size and Start Rotation
图 15.7:更新粒子系统的设置
Figure 15.7: Updating the settings of the particle system
图 15.8:添加爆发
Figure 15.8: Adding a burst
图 15.9:改变形状
Figure 15.9: Changing the shape
图 15.10:Size over Lifetime 属性
Figure 15.10: The Size over Lifetime property
图 15.11:按速度旋转属性
现在,我们已经完成了粒子系统的属性设置。最后,我们将选择循环和唤醒时播放,但现在,我们将取消选择它们,以便我们可以看到游戏进行时粒子系统不断播放。
Figure 15.11: The Rotation by Speed property
And now, we’re done with the properties of the Particle System. Eventually, we will select Looping and Play on Awake, but for now, we will leave them deselected so that we can see the particle system constantly playing when the game is playing.
选择图层下拉菜单并选择添加图层…。
Select the Layers dropdown menu and select Add Layer….
设置UI Particles Camera的Culling Mask属性为仅显示UI Particles,设置Main Camera的Culling Mask属性为排除UI Particles。
Set the Culling Mask property of UI Particles Camera to only display UI Particles and the Culling Mask property of Main Camera to exclude UI Particles.
图 15.12:分配目标纹理
Figure 15.12: Assigning the Target Texture
使用Create | UI | Canvas创建一个新的 UI Canvas 。将 Canvas 重命名为Particle Canvas。
Create a new UI Canvas with Create | UI | Canvas. Rename the Canvas to Particle Canvas.
图 15.13:分配给 Texture 属性的渲染纹理
Figure 15.13: The Render Texture assigned to the Texture property
这就是在UI中显示的粒子系统的设置。
That’s it for setting up a particle system that displays in a UI.
现在粒子系统已设置为显示在 UI 前面,我们可以设置逻辑以使动画按正确的顺序触发。为此,请执行以下步骤:
Now that the particle system is set to display in front of the UI, we can set up the logic to have the animation trigger in the correct order. To do that, go through the following steps:
公共粒子系统星星;
无效播放粒子(){
如果(!星星正在播放){
星星.播放();
}
}public ParticleSystem stars;
void PlayTheParticles() {
if (!stars.isPlaying) {
stars.Play();
}
}图 15.14:粒子动画事件
Figure 15.14: The particle Animation Event
现在玩游戏应该会导致所有动画在适当的时间播放,并且粒子系统当箱子打开时显示。战利品箱动画教程到此结束!
Playing the game now should result in all animations playing at the appropriate times and the particle system displaying when the chest opens. And that concludes the loot box animation tutorial!
乍一看,您似乎无法在设置为屏幕空间 - 叠加的 Unity UI 中使用 Unity 粒子。但是,有一些简单的技巧可以让您在 UI 中实现粒子效果并让粒子在它们前面渲染。
At first glance, it would appear that you cannot use Unity particles within Unity UI that is set to Screen Space - Overlay. However, there are a few simple tricks that let you implement particle effects in the UI and have the particles render in front of them.
在下一章中,我们将讨论如何使用世界空间画布渲染模式让 UI 元素直接出现在 Unity 场景中而不是屏幕上。
In the next chapter, we will discuss how to use the World Space Canvas Render Mode to have UI elements appear directly in your Unity scene rather than on the screen.
在第 6 章中,我们讨论了可以分配给 Canvas 的三种不同的渲染模式。我们使用了屏幕空间-叠加和屏幕空间-相机,但还没有使用世界空间。如第 2 章所述,在世界空间中渲染的 UI 直接放置在场景中。我们已经讨论了世界空间 Canvas 渲染的属性,因此本章将仅介绍何时使用它以及实现示例。
In Chapter 6, we discussed the three different Render Modes you can assign to a Canvas. We’ve used Screen Space-Overlay and Screen Space-Camera but haven’t used World Space yet. As described in Chapter 2, UI rendered in World Space is placed directly in the scene. We’ve already discussed the properties of World Space Canvas rendering, so this chapter will just look at when to use it and examples of implementation.
在本章中,我们将讨论以下主题:
In this chapter, we will discuss the following topics:
您可以在此处找到本章节的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2016
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2016
您可能想要使用的原因有很多世界空间画布。使用此渲染模式的最常见原因如下:
There are many reasons you may want to use a World Space Canvas. The most common reasons for using this render mode are the following:
例如,游戏Mojikara: Japanese Trainer使用世界空间画布来旋转面板,并将 UI 对象(如文本)附加到 3D 对象上。从以下屏幕截图中可以看到,左侧的面板在 3D 空间中略微旋转,因为它位于世界空间中空间画布:
For example, the game Mojikara: Japanese Trainer uses World Space Canvases to have rotated Panels and keep UI objects, such as Text, attached to 3D objects. As you can see from the following screenshot, the Panel on the left is rotated just slightly in 3D space, because it is on a World Space Canvas:
图 16.1:Mojikara:日语训练师(图片由 Intropy Games 的 Lisa Walkosz-Migliacio 提供)
Figure 16.1: Mojikara: Japanese Trainer (image provided by Lisa Walkosz-Migliacio, Intropy Games)
另一个旋转 UI 示例可以在游戏Cloudbase Prime中找到,如以下屏幕截图所示。它还使用世界空间渲染来创建悬停在物体和角色上方的指示器。
Another example of rotated UI can be found in the game Cloudbase Prime, as shown in the following screenshot. It also used World Space rendering to create indicators that hover over objects and characters.
图 16.2:Cloudbase Prime(图片由 Floating Island Games 的 Tyrus Peace 提供)
Figure 16.2: Cloudbase Prime (image provided by Tyrus Peace, Floating Island Games)
Cloudbase Prime中的所有 UI都已完成在世界空间画布上。这使开发人员能够创建炫酷的曲线 UI,如下所示:
All the UI in Cloudbase Prime was done on World Space Canvases. This allowed the developer to create cool curving UI, as follows:
图 16.3:Cloudbase Prime(图片由 Floating Island Games 的 Tyrus Peace 提供)
Figure 16.3: Cloudbase Prime (image provided by Tyrus Peace, Floating Island Games)
在这里,您可以看到 UI 在编辑器中的外观以及它在玩家眼中的外观。这可以很好地展示 UI 的构建方式:
Here, you can see how the UI looks in the Editor versus how it looks to the player. This gives a nice peek at how the UI was built:
图 16.4:Cloudbase Prime(图片由 Floating Island Games 的 Tyrus Peace 提供)
Figure 16.4: Cloudbase Prime (image provided by Tyrus Peace, Floating Island Games)
我建议你看看以下内容网站查看Cloudbase Prime实现 World Space UI 的更多方式,因为它们真的很漂亮:https ://imgur.com/a/hxNgL 。
I recommend checking out the following site to see more ways in which Cloudbase Prime has implemented World Space UI, as they are truly beautiful: https://imgur.com/a/hxNgL.
World Space UI 的另一个常见用途是模拟场景中的计算机屏幕和显示器。例如,我为朋友的 VR 游戏Cloud Rise构建了以下 UI 。通过将 World Space Canvas 放置在游戏屏幕的正上方来模拟显示器。然后我能够轻松地锚定和设置 UI 的动画;以同样的方式,我在屏幕空间中渲染了 UI 。
Another common usage of World Space UI is simulating computer screens and monitors within a scene. For example, I built the following UI for a friend’s VR game named Cloud Rise. The monitor was simulated by placing a World Space Canvas right on top of the in-game screen. I was then able to easily anchor and animate the UI; in the same way, I rendered the UI in Screen Space.
图 16.5:Cloud Rise(图片由 Bedhouse Games 的 Meredith Wilson 提供)
Figure 16.5: Cloud Rise (image provided by Meredith Wilson, Bedhouse Games)
一般情况下,VR 游戏的交互 UI 都在 World Space Canvas 上,因为玩家无法与屏幕交互。VR UI的用法是平面浮动面板或包裹面板。
In general, the interactive UIs of VR games are on World Space Canvases since the player cannot interact with the screen. Common usages of VR UI are flat floating Panels or wrapping Panels.
悬停指示器是迄今为止 World Space UI 最常见的用途;它们专门用于游戏内角色头顶上的生命条,如下面的Iris Burning屏幕截图所示:
Hovering indicators are by far the most common use of World Space UI; they are specifically used for health bars over the heads of in-game characters, as shown in the following screenshot of Iris Burning:
图 16.6:鸢尾花燃烧(图片由 William Preston、DCM Studios 提供)
Figure 16.6: Iris Burning (image provided by William Preston, DCM Studios)
大多数人在想到世界空间 UI 时都会想到 3D 游戏,因为他们会想到看起来很远的UI ,但它也常用于 2D 游戏!管理和 RTS 游戏经常使用 UI 来创建按钮和进度条,其他 UI 元素则保持其位置与其交互的对象相关联。世界空间 UI 可以位于一个包含屏幕上所有项目的 Canvas 上,其中各个 UI 项目与它们所代表的项目的 2D 世界空间坐标相匹配,或者它们可以位于各自项目的单独 Canvas 上。我们将在本章末尾的示例中介绍如何使用世界空间 UI 创建 2D 游戏。
Most people think of 3D games when they think of World Space UI because they think of UI that appears far away, but it is commonly used in 2D games as well! Management and RTS games use UI quite frequently to create buttons and progress bars, and other UI elements maintain their position with the object they interact with. The World Space UI can be on one Canvas that encompasses all items on the screen, with individual UI items matching the 2D World Space coordinates of the items they represent, or they can be on individual Canvases of their respective items. We will cover how to create a 2D game using World Space UI in an example at the end of the chapter.
现在,让我们探索如何使用 World Space UI。
Now, let’s explore how to use World Space UI.
每当创建 Canvas 时,它都会以Screen Space - Overlay作为其Render Mode进行初始化。因此,当您将Render Mode属性更改为World Space时,Canvas 在场景中将变得非常大。
Whenever a Canvas is created, it is initialized with Screen Space - Overlay as its Render Mode. Therefore, when you change the Render Mode property to World Space, the Canvas will be huge in your scene.
当您将画布缩小到在场景中,文本可能会非常模糊或根本看不见。假设我们在屏幕空间 - 覆盖中创建了以下画布,但决定将其放在世界空间中:
When you scale down the Canvas to the appropriate size in the scene, the text will likely be super blurry or not visible at all. Let’s say we created the following Canvas in Screen Space - Overlay but decided to put it in World Space:
图 16.7:屏幕空间中的画布 - 覆盖
Figure 16.7: A Canvas in Screen Space - Overlay
将其转换为世界空间最初不会导致任何问题,但是一旦我们将其缩小到宽度为4和高度为3(因为它最初是用 4:3 的宽高比屏幕创建的),文本就会消失!
Converting it to World Space doesn’t initially cause any problems, but once we scale it down to something like a Width of 4 and Height of 3 (since it was initially created with a 4:3 aspect ratio screen), the text will disappear!
图 16.8:屏幕空间中的画布 - 覆盖
Figure 16.8: A Canvas in Screen Space - Overlay
如果我将文本设置为允许水平溢出和垂直溢出,你会发现它在比较时很大到画布上!在下面的截图中,中间的小矩形就是画布:
If I set the Text to allow Horizontal Overflow and Vertical Overflow you’ll see that it is huge when compared to the Canvas! In the following screenshot, the tiny rectangle in the middle is the Canvas:
图 16.9:带有巨大溢出文本示例的世界空间画布
Figure 16.9: World Space Canvas with huge overflowing text example
为了解决这个问题,并让它看起来像我们想要的那样,我们需要调整Canvas Scaler组件上的Dynamic Pixels Per Unit属性(最初在第 6 章中讨论过)。此属性最初设置为1。
To fix this, and to get it looking the way we want, we need to adjust the Dynamic Pixels Per Unit property on the Canvas Scaler component (initially discussed in Chapter 6). This property is initially set to 1.
通常,为了确定新的动态像素大小值,我会在缩小画布之前取其起始宽度(即905 ),将其除以新的宽度 4,然后将该除法输入到我的动态像素每单位属性中。(在框中输入实际除法905/4将执行计算。)
Usually, to determine the new Dynamic Pixel Size value, I take the starting Width of the Canvas before I scale it down, which is 905, divide it by the new Width 4, and enter that division in my Dynamic Pixels Per Unit property. (Typing the actual division 905/4 in the box will perform the calculation.)
图 16.10:动态像素/单位属性已调整
Figure 16.10: Dynamic Pixels Per Unit property adjusted
然而,这一计算并没有得到这正是我想要的。因此,我增加了尺寸,直到看起来合适为止:
However, that calculation didn’t get the exact look I was looking for. So, I increased the size until it looked right:
图 16.11:第 16 章场景中的动态每单位像素属性示例
Figure 16.11: Dynamic Pixels Per Unit property example in the Chapter16 scene
每次更改画布的宽度和高度时,您都必须调整动态像素单位属性。减小画布的大小意味着增加动态像素单位属性,而增加画布的大小意味着减小每单位动态像素属性的大小。
Every time you change the Width and Height of the Canvas, you will have to adjust the Dynamic Pixels Per Unit property. Decreasing the size of the Canvas will mean increasing the Dynamic Pixels Per Unit property, and increasing the size of the Canvas will mean decreasing the size of the Dynamic Pixels Per Unit property.
这里有两个 Canvas,大小均为上图中的四分之一。在顶部 Canvas 中,我将 Width和Height更改为1和.75。在底部 Canvas 中,我将Scale X和Scale Y更改 为0.25:
Here are two Canvases, both one-fourth the size of the one from the previous figure. In the top Canvas, I changed the Width and Height to 1 and .75. In the bottom Canvas, I changed the Scale X and Scale Y to 0.25:
图 16.12:宽度和高度变化示例
Figure 16.12: Width and Height change examples
在第一个例子中,由于我改变了画布为四分之一大小,我在动态像素每单位属性中输入350*4,它自动为我计算了1400(我喜欢 Unity 在框中执行计算)。
In the first example, since I changed the Width and Height of the Canvas to one-fourth of the size, I typed 350*4 in the Dynamic Pixels Per Unit property, and it automatically calculated 1400 for me (I love that Unity performs calculations in the boxes).
但是,在第二个 Canvas 中,我不需要更改动态像素每单位大小,因为以这种方式使用Scale属性进行缩放不需要我更改它。
However, in the second Canvas, I did not have to change the Dynamic Pixels Per Unit size, because scaling with the Scale property in this way does not require me to change it.
从中可以得出的结论是,如果您的文本没有显示或者看起来非常模糊,请调整动态像素每单位属性,直到它看起来应该正常,或者通过调整其比例而不是其宽度 和高度来缩放您的画布。
The takeaway from this is that if your text isn’t displaying or looks incredibly blurry, adjust the Dynamic Pixels Per Unit property until it looks the way it should, or scale your Canvas by adjusting its Scale and not its Width and Height.
文本缩放将是最重要的考虑使用 World Space UI,但让我们回顾一些其他重要主题。
Text scaling will be the most important consideration with using World Space UI, but let’s review some other important topics.
在大多数情况下,使用 UI世界空间与在屏幕或相机空间中使用 UI 并无太大区别。不过,您需要记住一些事项。
For the most part, working with UI in World Space isn’t much different than working with UI in Screen or Camera Space. There are a few things you have to keep in mind, though.
在处理 3D 场景时,你可能希望 UI 始终面向玩家,无论玩家如何转动相机——这被称为广告牌效果。你可以在Update()函数中对对象的变换使用一个简单的LookAt()函数来实现这一点:
When working with 3D scenes, you may want your UI to always face the player, regardless of how the player turns the camera—this is known as a billboard effect. You can achieve this with a simple LookAt() function on the transform of the object in the Update() function:
变换.观察(2*变换.位置-相机.变换.位置);
transform.LookAt(2*transform.position-theCamera.transform.position );
您可以使用上述代码的变体,具体取决于您希望的旋转方式。
You can use a variation of the preceding code, depending on how you want the rotation to behave.
3D 世界空间 UI 的另一个考虑因素是它与相机的距离。您可能希望 UI 仅在与相机有特定距离时才渲染,因为距离太远时可能难以看清。
Another consideration with 3D World Space UI is the distance it is away from the camera. You may want to have UI only render when it is a specific distance from the camera as it may be difficult to see when it is too far away.
根据您的项目,使用世界空间画布可能会导致光线投射困难,从而导致与 UI 交互成为问题。Floating Island Games 的 Tyrus Peace 建议,如果您最终必须创建自己的光线投射系统,请创建自己的物理层,就像他使用Cloudbase Prime所做的那样,如本章前面所示。
Depending on your project, using World Space Canvases may cause difficulties with Raycasting, making interacting with UI a problem. Tyrus Peace of Floating Island Games recommends creating your own physics layer if you end up having to create your own Raycasting system, as he did with Cloudbase Prime, shown earlier in the chapter.
使用世界空间画布与使用屏幕和相机空间中的画布没有太大区别。世界空间画布有很多好处。如果你的场景中有一个对象,并且它的 UI 明确地与它的位置绑定,那么使用世界空间画布会很有帮助,这样 UI 就会跟随它,无论它在哪里。这样就无需尝试将对象的世界空间坐标转换为屏幕坐标,以确保 UI 始终与对象对齐。它还保证 UI 对象始终会根据对象的位置正确显示,即使屏幕的分辨率发生变化。在本章中,我将介绍世界空间画布的两种常见用途:一种在 2D 空间中,另一种在 3D 空间中。让我们从2D 空间开始。
Working with World Space Canvases isn’t significantly different than working with Canvases in Screen and Camera Space. World Space Canvases offer many benefits. If you have an object that exists within your scene that has UI specifically tied to its location, it is helpful to use a World Space Canvas so that the UI follows it wherever it is. This removes the necessity of trying to convert the object’s World Space coordinates to Screen coordinates to ensure that the UI always lines up with the object. It also guarantees that the UI object will always display correctly with respect to the object’s location, even when the screen’s resolution changes. In this chapter, I will cover two common uses of World Space Canvases: one in 2D space and another in 3D space. Let’s begin with 2D space.
在这个例子中,我们将开始一个新场景。为了让您不必构建场景,我们将从包含所有内容的资源包开始所需物品。
For this example, we will start a new scene. For you to not have to build out the scene, we will start with an Asset package that includes all the required items.
我们将创建一个 UI,让角色的头顶弹出一个状态指示器。场景播放 3 秒后,角色头顶会出现一个状态指示按钮。玩家点击状态指示器后,会出现一个对话框。5 秒后,对话框会消失。状态指示器将在 10秒后重新出现。
We’ll create UI that allows a character to have a status indicator pop up above his head. After the scene has played for 3 seconds, a status-indicating button will appear over the character’s head. Once the player clicks on the status indicator, a dialog will appear. After 5 seconds, the dialog will disappear. The status indicator will re-appear 10 seconds later.
图 16.13:2D 世界空间 UI 示例
Figure 16.13: 2D World Space UI example
本例中使用的艺术作品来自https://opengameart.org/content/medieval-rts-120。
The art used in this example was accessed from https://opengameart.org/content/medieval-rts-120.
要创建上例所示状态指示 UI,请完成以下步骤:
To create the status-indicating UI demonstrated by the previous example, complete the following steps:
右键单击层次结构中的Mage并添加 UI Canvas 作为 Mage 的子项。
Right-click on the Mage in the Hierarchy and add a UI Canvas as a child of the Mage.
图 16.14:选择 UI 旋钮图像
Figure 16.14: Selecting the UI Knob image
图 16.15:将感叹号移到法师的头上
Figure 16.15: Moving the exclamation point over the Mage’s head
图 16.16:警报按钮的 On Click() 事件
Figure 16.16: The On Click() event of the Alert Button
图 16.17:对话框
Figure 16.17: The Dialog box
如果您玩游戏,您将在 3 秒后看到感叹号按钮出现。单击按钮将使文本出现。尝试在场景中移动 Mage 角色。您会看到,无论他在哪里,感叹号按钮和文本都会出现在他的头上。这是一种非常有用的技术,可以创建与移动角色保持一致的 UI 元素。
If you play the game, you’ll see the exclamation point Button appear after 3 seconds. Clicking on the Button will make the Text appear. Try moving the Mage character around in the scene. You’ll see that no matter where he is, the exclamation point Button and Text appear over his head. This is a really helpful technique for creating UI elements that stay with moving characters.
我知道现在的例子有点无聊,但我建议使用前两章讨论的一些技巧,为感叹号添加一个漂亮的弹跳动画并让文本淡入淡出吨。
I know that the example is a bit boring the way it is now, but I recommend using some of the techniques discussed in the previous two chapters to add a nice bouncy animation to the exclamation point and have the Text fade in and out.
在 3D 场景中制作世界空间 UI 需要一点时间如果相机可以在 3D 空间中旋转和移动,则比在 2D 场景中制作世界空间 UI 的工作量更大。如果相机可以移动和旋转,则 UI 可能需要始终面向相机。否则,玩家将无法看到UI 元素。
Making World Space UI in a 3D scene takes a little more work than making World Space UI in a 2D scene if the camera can be rotated and moved throughout the 3D space. If the camera can move and rotate, the UI likely needs to constantly face the camera. Otherwise, the player will not be able to see the UI elements.
在本例中,我们将再次创建一个新场景。为了让您不必从头开始构建场景,我们将从包含所有必需项目的资产包开始。
For this example, we will once again create a new scene. For you to not have to build the scene from scratch, we will start with an Asset package that includes all the required items.
我们将创建一个简单的悬浮生命条,始终面向摄像头。它还将接收点击,以便我们观察生命条的减少:
We’ll create a simple hovering health bar that constantly faces the camera. It will also receive clicks so that we can watch the health bar reduce:
图 16.18:宇航员的健康条悬停
Figure 16.18: Astronaut with a hovering health bar
本例中使用的艺术作品来自https://opengameart.org/content/space-kit。
The art used in this example was accessed from https://opengameart.org/content/space-kit.
要创建一个始终面向摄像机并接收玩家点击输入的生命值条,请完成以下步骤:
To create a health bar that always faces the camera and receives player-click input, complete the following steps:
在层次结构中右键单击宇航员,然后添加 UI Canvas 作为宇航员的子项。
Right-click on the astronaut in the Hierarchy and add a UI Canvas as a child of the astronaut.
图 16.19:将画布缩放至宇航员上方
Figure 16.19: Scaling the Canvas over the astronaut
图 16.20:向按钮添加空白图像
Figure 16.20: Adding a blank image to the Button
图 16.21:生命值条的超大文本
Figure 16.21: The oversized text of the Health Bar
图 16.22:生命值条的超大文本
Figure 16.22: The oversized text of the Health Bar
笔记
Note
当尝试让文本在 3D 空间中看起来美观时,如果仅更改动态像素单位大小会导致文本不连贯,请更改属性,直到文本在场景中看起来非常清晰。然后,结合更改Rect Transform组件的Scale和Text 对象的Font Size来找到最佳点。
When trying to get the text to look nice in 3D space, if only changing the Dynamic Pixels Per Unit Size results in choppy text, change the property until the text looks perfectly crisp in the scene. Then, use a combination of changing the Rect Transform component’s Scale and Font Size of the Text object to find the sweet spot.
图 16.23:向左伸展的锚点
Figure 16.23: Left-stretching anchor
图 16.24:游戏对象的层次结构
Figure 16.24: The Hierarchy of GameObjects
图 16.25:生命值条的 OnClick() 事件
现在玩游戏应该会导致当您单击“健康栏按钮”时“健康填充”减少其填充值:
图 16.26:健康栏按钮减少
Figure 16.25: The OnClick() event of the Health Bar
Playing the game now should result in the Health Fill reducing its fill value when you click on the Health Bar Button:
Figure 16.26: The Health Bar button reducing
公共相机theCamera;
无效更新()
{
变换。查看(2 * 变换。位置 - 相机。变换。位置);
}public Camera theCamera;
void Update()
{
transform.LookAt(2 * transform.position - theCamera.transform.position);
}如果你现在玩这个游戏,你会看到当你移动相机时,生命值条始终面向相机。尝试更改场景中相机的变换位置,以更显著地观察LookAt()函数的工作情况。
If you play the game now, you’ll see that as you move the camera around, the health bar always faces the Camera. Try changing the Transform position of the camera in the scene to see the LookAt() function work more drastically.
世界空间UI 的实现与在相机或屏幕空间中渲染的 UI 并没有太大区别。将 UI 添加到世界空间可让您创建炫酷的效果,并让您更好地控制 UI 相对于场景中对象的位置。
World Space UI is not significantly different in its implementation than UI that renders in the Camera or Screen Space. Adding UI to your World Space gives you the ability to create cool effects and gives you more control over your UI’s position relative to objects in the scene.
在下一章中,我们将讨论如何优化Unity UI。
In the next chapter, we will discuss how to optimize Unity UI.
优化是我们用来确保游戏运行顺畅且帧率一致的过程。通过优化,我们首先找到游戏中降低游戏性能的资源,然后实施可提高性能的解决方案。
Optimization is the process that we use to make sure that our game runs smoothly and the framerate is consistent. Through optimization we first locate resources within our game that are reducing our game’s performance and then implement solutions that will improve that performance.
很多因素都可能导致游戏性能不佳或帧率低下。这可能包括未优化的照明、编写不当的脚本、庞大的资源以及不当的 UI 构造。由于这是一本关于 UI 的书,我们将只关注如何改进 UI 构造来提高性能。
There are lots of things that can cause a game to have poor performance or low framerate. This can include things like unoptimized lighting, poorly written scripts, large assets, and improper UI construction. Since this is a UI book, we’ll focus only on how improving your UI construction can improve your performance.
在本章中,我将讨论以下内容:
In this chapter, I will discuss the following:
在优化 UI 之前,您需要了解如何判断其性能是否良好。让我们回顾一下图形渲染的一些基本术语和原理。
Before you can optimize your UI, you need to learn how to tell if it is performant or not. Let’s review some basic terms and principles of graphics rendering.
笔记
Note
本章将仅介绍基本级别的性能分析,重点将更多地放在您可以做的事情上,以构建更好的 UI。如果在本章结束时,您想了解有关性能分析的更多信息,我建议您使用以下资源: https: //unity.com/how-to/best-practices-for-profiling-game-performance#gpu-bound
This chapter will only cover performance profiling on a basic level and its focus will be more on things you can do to build better UI. If, by the end of the chapter, you’d like more information on performance profiling, I suggest the following resource: https://unity.com/how-to/best-practices-for-profiling-game-performance#gpu-bound
正如我之前所说,优化是我们用来确保应用程序顺利运行的过程,帧率是一致的。我们希望优化我们的游戏,以确保所有玩家无论在什么条件下玩游戏都能获得相同的体验。因此,例如,如果我们制作的是 PC 游戏,我们希望确保每个玩家无论机器的性能如何都能获得相同的体验。我们还希望确保如果玩家在屏幕上渲染很多东西,游戏不会比在屏幕上渲染较少东西时滞后。
As I stated earlier, optimization is the process that we use to make sure that our application runs smoothly, and the framerate is consistent. We want to optimize our game to ensure that all players have the same experience regardless of the conditions in which they are playing. So, for example, if we are making a PC game, we want to make sure that every player has the same experience regardless of the power of their machine. We also want to make sure if a player has many things rendering on the screen, the game does not lag compared to when there were less things rendering on the screen.
我们不希望帧率降低,也不希望输入延迟。这对于游戏等游戏来说非常重要。对于第一人称射击游戏或平台游戏等游戏来说,输入延迟可能意味着玩家输掉比赛或掉下悬崖。
We do not want the frame rate to reduce, and we do not want the inputs to lag. This is extremely important for things like games. In something like a first-person shooter or platform this could a lag in input could mean a player loses a match or falls off a cliff.
让我们回顾一下讨论优化时经常听到的一些基本术语。首先,我们先来了解一下确定应用程序性能的常用指标。
Let’s review some basic terminology that you hear often when discussing optimization. First, we’ll start with a common metric for determining an application’s performance.
帧率是我们衡量的一个指标应用程序的优化。这是一个很好的指标,因为它不仅仅是用户永远不会注意到的后台发生的事情。用户可以看到并注意到帧速率的变化,因此测量其性能可以衡量我们的用户如何体验我们的游戏。
Framerate is one metric in which we measure our application’s optimization. It’s a good metric, because it’s not just something that is happening in the background that the user never notices. Users can see and notice changes in framerate, so measuring its performance measures how our users experience our games.
帧速率可以用每秒帧数( fps ) 或毫秒来衡量。目标是拥有一致的帧速率无论游戏中发生什么。
Framerate can be measured in frames per second (fps) or time in milliseconds. The goal is to have a consistent framerate regardless of what is happening in the game.
当以每秒帧数来衡量帧速率时,每个单独的帧都会在一定时间内渲染出来。假设你的目标是 60 fps。你需要确保应用程序的所有方面都能以 60 fps 的速度运行。因此,你的目标是每秒拥有一致的帧数。因此,当我们设置 fps 基准时,这意味着我们希望我们的应用程序在所有部分都以该 fps 一致运行。我们不希望我们的游戏一开始以 60 fps 运行,然后在我们的UI 打开时下降。
When measuring framerate in frames per second, each individual frame renders out in a certain amount of time. Let’s say you have a goal of 60 fps. You would need to make sure that all aspects of your application can run at 60 fps. So, your goal is to have a consistent number of frames per second. Therefore, when we set a fps benchmark, that means we want our application to run at that fps consistently across all parts of the application. We don’t want our game to run at 60 fps at the beginning and then dip when our UI opens.
测量帧速率的另一种方法是以毫秒为单位的时间。每帧都需要一定的时间来渲染。我们希望每帧的时间尽可能短。每帧 10 毫秒相当于 60 fps,以毫秒为单位是一个不错的时间。
Another way to measure framerate is time in milliseconds. Every frame takes some amount of time to render. We want that amount of time to be as low as possible for each frame. 10 ms per frame is the equivalent of 60 fps and is a good time in milliseconds.
既然我们已经讨论过了什么是帧率,我们先来说说我们的电脑上的资源用在了哪里。
Now that we’ve discussed what frame rate is, let’s talk about where resources are used on our computer.
当尝试优化时在您的游戏中,您将调查游戏的资源,以确定使用最多资源的地方。您需要确定问题是否出在GPU 或 CPU。中央处理器( CPU ) 是计算机的大脑。图形处理器( GPU ) 负责渲染图像。我们的资源是在GPU还是在CPU上将决定优化方案。
When trying to optimize your game, you will investigate your game’s resources to determine where the most resources are being used. You will want to identify whether the issues are on the GPU or CPU. The Central Processing Unit (CPU) is the brain of the computer. The Graphics Processing Unit (GPU), is what renders images. Whether our resources are on the GPU or CPU will determine the optimization solution.
问题示例CPU 的问题在于指令太多,这意味着运行的脚本太多,或者脚本运行时间太长。GPU 的问题往往意味着渲染的东西太多。然而,CPU 仍然在渲染中发挥作用,因为 CPU 发出指令并告诉 GPU 要渲染什么。在讨论 GPU 和 CPU 时,你经常会听到术语“绘制调用”。绘制调用是指CPU 告诉 GPU 它需要在屏幕上绘制(或显示)某些内容。一般来说,您需要减少游戏的绘制调用次数。
Examples of problems on the CPU are too many instructions, meaning too many scripts are running or the scripts take too long to run. Problems with the GPU tend to mean too many things are being rendered. The CPU does still play a part in rendering, however, as the CPU gives out the instructions and tells the GPU what to render. Often when discussing GPU and CPU, you’ll hear the term draw calls. A draw call is when the CPU tells the GPU it needs to draw (or display) something on the screen. In general, you want to reduce the number of draw calls your game makes.
我们将在本章的后续章节中更深入地讨论 GPU 和 CPU,以及我们在设计 UI 时做出的各种选择如何受到 GPU 和 CPU 的影响。
We’ll discuss GPU and CPU and how various choices we make designing our UI are affected by GPU and CPU more thoroughly in future sections of this chapter.
现在我们已经介绍了一些优化的基本概念,让我们回顾一下Unity提供的一些可以帮助我们进行优化的工具。
Now that we’ve covered some basic concepts of optimization, let’s review some tools provided by Unity that help us with optimization.
在确定你的游戏性能,您可以使用所谓的基准测试。基准测试可以让你了解你的应用程序在各种条件下的运行情况。次。在进行基准测试时,您会收集性能指标并建立基线或基准指标。然后,您将后续结果与该基准进行比较,以查看指标是否有所改善或恶化。这可以让您知道所做的更改是否影响了游戏的性能。
When determining your game’s performance, you may use what is called benchmarking. Benchmarking allows you to see how well your app is running at various times under given conditions. When benchmarking, you collect performance metrics and establish a baseline, or benchmark metric. You then compare subsequent results to that benchmark to see if the metric has improved or worsened. This lets you know if changes you have made have affected your game’s performance.
Unity 提供了一些工具,可以帮助你评估游戏的性能,具体方法如下:性能指标。现在让我们看看这些工具。
There are a few tools provided by Unity that can help you assess the performance of your game by providing you performance metrics. Let’s look at those tools now.
笔记
Note
了解环境和基准测试的内容。建议您在目标平台上进行基准测试。这样您就可以确定计划运行游戏的最低标准设备。有了更好的设备,您的游戏性能才会更好。
Be aware of the environment and what you are benchmarking. It’s recommended that you benchmark on your target platform. That way you can determine the minimum standard device you plan to run your game on. The performance of your game will only get better with a better device.
一个简单的方法来查看游戏表现是通过游戏视图的统计窗口(或统计窗口)。
One simple way to view the performance of your game is through the Statistics window (or Stats windows) of the Game view.
图 17.1:分析统计窗口
Figure 17.1: Analyzing the Stats Window
如果您选择游戏视图右上角的“统计”按钮,您将看到有关项目的各种信息。在“图形”部分下,您将看到每秒帧数的帧速率以及以毫秒为单位的时间。这将根据每一帧不断变化。您还可以看到有关 CPU 主线程和渲染线程的信息。
If you select the Stats button in the top right corner of your Game view, you’ll see various information about your project. Under the Graphics section, you’ll see frame rate in both frames per second and time in milliseconds. This will continuously change as it is based on each frame. You also see information about the CPU main thread and render thread.
如果你使用帧速率作为基准测试中,您可以在此处查看帧速率值并了解它们随时间的变化情况。请记住,目标是保持一致性。
If you’re using frame rate as your benchmark, you can watch the framerate values here and see how they change over time. Remember, the goal is consistency.
笔记
Note
您可以在此处了解有关统计窗口的更多信息: https: //learn.unity.com/tutorial/working-with-the-stats-window-2019-3 ?uv=2019.4#
You can learn more about the stats window, here: https://learn.unity.com/tutorial/working-with-the-stats-window-2019-3?uv=2019.4#
统计信息窗口提供了一些关于游戏性能的基本信息。但是,如果你想要更深入地了解游戏的运行情况,您可以使用Unity Profiler。
The Stats window gives some at a glance basic information about your game’s performance. However, if you want a more in-depth breakdown of how your game is running, you can use the Unity Profiler.
Profiler 向您显示详细信息关于你的游戏性能。要查看 Profiler,请选择窗口|分析| Profiler。玩游戏时,您可以看到哪些项目导致了性能问题。
The Profiler shows you detailed information about your game’s performance. To view the Profiler, select Window | Analysis | Profiler. When you play the game, you can then see which items are responsible for performance issues.
图 17.2:查看 Unity Profiler
Figure 17.2: Viewing the Unity Profiler
在Profiler中,您可以仅选择 UI 模块来缩小每个单独的 UI 元素对性能的影响范围。
Within the Profiler, you can select the UI Modules only to narrow down how each of your individual UI elements are affecting your performance.
图 17.3:仅启用 UI Profiler 模块
Figure 17.3: Enabling only the UI Profiler Modules
After doing so, you can see information about each individual Canvas.
图 17.4:观察 Profiler 中的 UI 对象
Figure 17.4: Observing the UI Objects in the Profiler
笔记
Note
您可以在此处了解有关 Unity Profiler 的更多信息及其使用方法:https://docs.unity3d.com/Manual/Profiler.xhtml
You can learn more about the Unity Profiler and how to use it here: https://docs.unity3d.com/Manual/Profiler.xhtml
如果你有兴趣游戏中的物品进行批处理后,您可以使用 Unity Frame Debugger。
If you’re interested in how items in your game are being batched, you can use the Unity Frame Debugger.
框架调试器可以帮助你排除故障您的 UI 存在批处理问题。您可以通过窗口|分析|帧调试器访问帧调试器。
The Frame Debugger can help you troubleshoot batching issues with your UI. You can access the Frame Debugger via Window | Analysis | Frame Debugger.
图 17.5:查看框架调试器
Figure 17.5: Reviewing the Frame Debugger
启用 Frame Debugger 后,您可以看到各种渲染事件。单击它们将在游戏窗口中前进,为您提供事件的预览。这将向您显示哪些项目正在组合成批次。
Enabling the Frame Debugger will allow you to see various rendering events. Clicking on them will step forward in the Game window, giving you a preview of the event. This will show you which items are being combined into batches.
笔记
Note
您可以在此处了解有关 Frame Debugger 的更多信息:https://docs.unity3d.com/Manual/frame-debugger-window.xhtml
You can learn more about the Frame Debugger here: https://docs.unity3d.com/Manual/frame-debugger-window.xhtml
现在我们已经讨论了优化的基础知识,我们可以开始讨论优化UI 的方法。
Now that we’ve discussed the basics of optimization, we can start talking about ways to optimize your UI.
请记住,每个 Canvas 都有自己的 Canvas Renderer 组件。Canvas 将所有元素组合成批并一起渲染。如果 Canvas 的几何图形需要重建,则该 Canvas 被视为脏的。优化 UI 的主要目标是减少 Canvas 或其元素被视为脏的次数,减少 Canvas 需要重新批处理的次数。考虑到这一点,让我们来看看一些优化Unity UI 的技巧。
Remember, each Canvas has its own Canvas Renderer component. Canvases combine all their elements into batches that are rendered together. A Canvas is considered dirty, if its geometry needs to be rebuilt. One of the main goals of optimizing UI is to reduce the number of times a Canvas or its elements are considered dirty, to reduce the number of times that the Canvas needs to be rebatched. With that in mind, let’s look at some techniques for optimizing Unity UI.
每当一个元素如果画布上的元素被修改,画布将被视为脏元素,并向 GPU 发送绘制调用。如果画布上有多个项目,则需要重新分析画布上的所有项目,以确定最佳绘制方式。因此,更改画布上的一个元素需要 CPU 重建画布上的每个元素,这可能会导致 CPU 使用率突然飙升。因此,您应该将 UI 放在多个画布上。
Whenever an element on a Canvas is modified, the Canvas is considered dirty, and a draw call is sent to the GPU. If there are multiple items on the Canvas, all the items on the Canvas will need to be reanalyzed to determine how best they should be drawn. So, changing one element on the Canvas requires the CPU to rebuild every element on the Canvas, potentially causing a sudden surge in CPU usage. Due to this, you should put your UI on multiple Canvases.
在确定如何对画布进行分组时,请考虑画布上的项目需要更改的频率。将所有静态 UI 元素与动态项目分组到不同的画布上是一种很好的做法。这样可以避免在动态项目发生变化时重新绘制静态项目。此外,根据动态元素的更新时间将其拆分到画布中,尽量将同时更新的元素放在一起。
When determining how to group your Canvases, consider how often the items on the Canvas will need to be changed. It is good practice to group all static UI element on separate Canvases from items that are dynamic. This will stop the static items from having to be redrawn whenever the dynamic items change. Additionally, split dynamic elements into Canvases based on when they will update, trying to keep ones that update at the same time together.
尝试减少画布上的绘制调用时需要考虑的其他方面是元素 z 坐标、纹理和材质。根据这些属性对 UI 元素进行分组也可以减少与UI 相关的 CPU 使用率。
Other aspects to consider when attempting to reduce draw calls on Canvases are the elements z-coordinates, textures, and materials. Grouping UI elements based on these properties can also reduce your CPU usage with regards to UI.
布局组非常当您需要适当间隔的 UI 时,这种方法非常有用;但是,它们效率极低。每当布局组中的元素发生变化时,该组中的每个元素都必须为每个具有布局组的父对象调用至少一个GetComponent。这使得嵌套布局组的性能非常低下。
Layout Groups are incredibly helpful when you want properly spaced UI; however, they are extremely inefficient. Whenever an element in a Layout Groups changes, every element in that group must call at least one GetComponent for each parent object that has a Layout Groups. This makes nested Layout Groups very non-performant.
为了避免这种情况,您可以简单地不使用布局组。相反,您可以使用锚点并编写自己的布局代码来动态放置项目。
To avoid this, you could simply not use Layout Groups. Instead, you could use Anchors and write your own layout code for dynamically placing items.
然而,完全避免使用它们并不一定对每个人来说都是最好的解决方案,所以如果你选择使用它们,请尝试避免嵌套布局组或包含大量子项的布局组。您还可以选择仅对动态元素使用布局组,并避免将其与静态元素一起使用。您还可以在动态 UI正确定位后立即禁用它们。
Avoiding them entirely isn’t necessarily the best solution for everyone, however, so if you do choose to use them, try to avoid nested Layout Groups or layout groups with very large amounts of children. You can also choose to use layout groups for dynamic elements only and avoid them with static elements. You can also disable them as soon as dynamic UI has been properly positioned.
如果你想隐藏元素Canvas,如果可能的话,最好禁用整个 Canvas 组件。请记住,Canvas 组件是渲染 UI 的组件,因此禁用 Canvas 组件将导致其停止渲染。建议您禁用 Canvas 组件,而不是简单地更改 Canvas 的可见性,因为即使 Canvas 组件不可见,它仍将继续进行绘制调用。禁用组件不会导致 Canvas 重建。这比启用和禁用 Canvas GameObject 更便宜,因为这将触发OnEnable和OnDisable方法。
If you want to hide elements on a Canvas, it is best to disable the entire Canvas component if possible. Remember, the Canvas component is what renders the UI, so disabling the Canvas component will cause it to stop rendering. It is recommended you disable the Canvas component rather than simply changing the visibility of the Canvas, because the Canvas component will continue to make draw calls, even if it is not visible. Disabling the component does not cause the Canvas to rebuild. This is less expensive than enabling and disabling the Canvas GameObject, as that will trigger the OnEnable and OnDisable methods.
如果您想用 UI 隐藏游戏中的所有内容,例如,您有一个完全覆盖屏幕的暂停菜单,则应停止摄像机渲染游戏中除 UI 之外的任何内容。例如,如果您有一个完全覆盖屏幕的菜单,即使您看不到菜单后面的项目,它们仍会被渲染。
If you want to hide everything in your game with UI, for example you have a pause menu that completely covers your screen, you should stop the camera from rendering anything in your game other than the UI. For example, if you have a menu that completely covers the screen, even though you cannot see the items behind the menu, they are still being rendered.
对象池是一种优化使用的技术减少可重复创建(或实例化)对象的次数。使用对象池时,您会在游戏开始时将一组禁用对象放入池中,然后,您无需在游戏进行时实例化这些对象,而是将它们从池中拉出。
Object pooling is an optimization technique used to reduce the number of times repeatable objects are created (or instantiated). When object pooling, you place a collection of disabled objects in a pool at the start of the game and then, rather that instantiating those objects while the game is playing, you pull them from the pool.
对象池是提高游戏性能的好方法,因为它通过创建可重用的对象集合来减少创建对象的次数。
Object pooling is a great way to improve the performance of your game, because it reduces the number of times an object has to be created by creating a collection of objects that can be reused.
笔记
Note
有关对象池概念的更多信息,请访问以下网站: https: //learn.unity.com/tutorial/introduction-to-object-pooling
For more information on the concept of object pooling, visit the following site: https://learn.unity.com/tutorial/introduction-to-object-pooling
由于重新设置 UI 对象会导致 Canvas 被标记为脏,因此在使用对象池时,您需要仔细安排禁用、启用和重新设置的时间。由于 Canvas 中元素的动态更改会导致 Canvas 向 GPU 发送绘制调用,因此在禁用项目之前或之后为其设置父级,可能会不必要地增加绘制调用次数。目标是不让池中的对象向 GPU 发送任何绘制调用。因此,池中的对象应始终只在禁用状态下作为池的父级。
Because reparenting UI objects causes a Canvas to be marked as dirty, you want to carefully time your disabling, enabling, and reparenting when using an object pool. Since dynamic changes to elements within a Canvas cause the Canvas to send a draw call to the GPU, inappropriately parenting items before or after disabling them, can double up your draw calls unnecessarily. The goal, is to not cause the objects in the pool to send any draw calls to the GPU. So, objects in the pool should only ever be parented to the pool in a disabled state.
如果你将物体放在对象池,禁用它,然后将其重新设置为池的父级。通过确保在将其放入池之前将其禁用,您将无需重建池。相反,如果您想从池中移除对象,请重新设置为它的父级,然后启用它。这也将消除池发送绘制调用的需要。
If you are placing an object in an object pool, disable it, then reparent it into the pool. By making sure it is disabled before being placed in the pool, you will remove the need for the pool to need rebuilding. Conversely, if you want to remove an object from the pool, reparent it, then enable it. This too will remove the need for the pool to send a draw call.
每个 UI 图像组件具有属性Raycast Target。默认情况下,此选项处于选中状态。如果您不希望 UI 元素阻止射线投射,请禁用此选项以减少射线投射检查。
Every UI Image component has the property Raycast Target. By default, this is selected. If you do not wish for your UI element to block raycasts, disable this to reduce raycasting checks.
图 17.6:Raycast Target 属性
Figure 17.6: The Raycast Target property
此外,如果您的 UI 根本无法交互,您可以删除 Graphic Raycaster 组件以消除其引起的不必要的计算。
Additionally, if your UI is not interactable at all, you can remove the Graphic Raycaster component to remove the unnecessary calculation it causes.
图 17.7:Graphic Raycaster 组件
Figure 17.7: The Graphic Raycaster component
使用基本的 Unity UI本章介绍的优化策略将帮助你创建不会对 UI 产生负面影响的 UI游戏的表现。
Using the basic Unity UI optimization strategies covered in this chapter will have you well on your way to creating UI that does not inversely affect the performance of your game.
在本章中,我们讨论了优化 Unity UI 的基础知识。我们回顾了 Unity 中优化的一些基本概念,研究了可用于评估游戏性能的工具,然后讨论了创建高性能 UI 的策略。
In this chapter, we discussed the basics of optimizing Unity UI. We reviewed some fundamental concepts of optimizing in Unity, looked at the tools that allow us to assess our game’s performance, and then discussed strategies for creating performant UI.
笔记
Note
本章仅重点介绍了如何通过 UI 提高游戏性能。但请记住,游戏的多个方面都可能导致其出现性能问题。有关如何提高游戏各个方面性能的良好资源,我建议查看 Unity 提供的有关优化的免费电子书:https://resources.unity.com/games/performance-optimization-e-book-console-pc
This chapter focused only on how to improve the performance of your game through UI. But remember there are multiple aspects of your game that can cause it to have performance issues. For a good resource on improving the performance of all aspects of your game, I suggest reviewing the free ebook provided by unity on optimization: https://resources.unity.com/games/performance-optimization-e-book-console-pc
在下一章中,我们将讨论 Unity 创建的一个新 UI 系统,称为新 Unity UI 工具包。它不仅可以在运行时创建 UI,还可以在编辑器中创建 UI。因此,它可以用来帮助您创建工具来改善您的工作流程。
In the next chapter, we’ll talk about a new UI system created by unity called the new unity UI toolkit. It can be used to create UI not only during runtime but also within the editor. Thus, it can be used to help you create tools to improve your workflow.
有关优化 UI 的更多信息,我推荐以下资源:
For more information on optimizing UI, I recommend the following resources:
在本部分中,您将不再探索 Unity UI 系统,而是了解 Unity 提供的另外两个用于创建 UI 的系统 - UI Toolkit 和 IMGUI。您还将了解如何使用新输入系统处理输入。
In this part, you’ll step away from exploring the Unity UI system and look at the two other systems provided by Unity to create UI – UI Toolkit and IMGUI. You’ll also look at how to handle input with the New Input System.
本部分包含以下章节:
This part has the following chapters:
到目前为止,本书的重点一直是Unity UI ( uGUI )。然而,Unity 一直在开发另一个可用于开发游戏 UI 的系统:UI Toolkit。它基于 Web 设计原则,旨在让您以比 uGUI 更灵活、更具扩展性的方式创建 UI。它通过将 UI 的设计和开发与场景和游戏对象分离来实现这一点,而是通过代码和样式表创建 UI — 就像在Web 设计中一样。
Up to this point, the focus of this book has been on Unity UI (uGUI). However, Unity has been developing another system in which you can develop UI for your game: the UI Toolkit. It is based on the principles of web design and is meant to allow you to create UI in a more flexible and extensible way than the uGUI. It accomplishes this by divorcing the design and development of UI from scenes and GameObjects and instead creates UI via code and style sheets—just like in web design.
UI Toolkit 是一个完全不同的系统,因此如果您习惯使用 uGUI 开发 UI,那么一开始使用它进行开发可能会感觉很不舒服。但是,如果您有使用.xhtml和.css的经验,或者使用XML开发 Android 或 iOS 界面的经验,那么这一切都应该非常熟悉。
The UI Toolkit is an entirely different system, so beginning development with it may feel jarring at first if you are used to developing UI with uGUI. However, if you have experience with .xhtml and .css or developing Android or iOS interfaces using XML, this should all feel very familiar.
由于 UI Toolkit 是一个完全不同的系统,所采用的原理与本书其他章节完全不同,因此,要完整解释您可以使用它做的所有事情,就需要专门写一本教科书。因此,本章将为您提供开始使用该系统所需的基本信息,但不会全面讨论它的每个方面。如果您想进一步学习 UI Toolkit,我将在本章中为您提供其他资源供您查看。
Since the UI Toolkit is an entirely different system using entirely different principles than the rest of the chapters in this book, to fully explain all that you could do with it would merit a whole other textbook devoted purely to it. Therefore, this chapter will give you the basic information you need to get started with using the system, but not fully discuss every aspect of it. I will give you additional resources throughout this chapter to review if you’d like to go even further with your study of the UI Toolkit.
在本章中,我将讨论以下内容:
In this chapter, I will discuss the following:
笔记
Note
本章假设您对 HTML 和 CSS 有粗略的了解。但是,如果您对 HTML 和 CSS 有更丰富的经验,那么采用 UI Toolkit 系统将变得容易得多。
This chapter assumes you have a cursory knowledge of HTML and CSS. However, having more extensive experience with HTML and CSS will make adopting the UI Toolkit system significantly easier.
在我们深入了解 UI 工具包的内部工作原理之前,让我们先回顾一下它的用例以及何时使用它。
Before we jump into the inner workings of the UI Toolkit, let’s review its use cases and when you will use it.
您可以在此处找到本章节的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2018
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2018
您可能还记得第 5 章中提到,Unity 中总共有三种 UI 系统。到目前为止,本书的重点是 Unity UI (uGUI),它可用于制作游戏内(又称运行时)UI。但是,如果你想制作可以在编辑器中查看的 UI,则必须使用不同的系统。可用于创建编辑器的两个系统UI 是IMGUI(我们将在下一章讨论)和 UI Toolkit。但是,IMGUI 只能用于制作编辑器 UI,而 UI Toolkit 既可用于制作运行时 UI,也可用于制作编辑器 UI。
As you may recall from Chapter 5, there are three total UI systems that can be used within Unity. Up to this point, the focus of this book has been on Unity UI (uGUI), which can be used to make in-game (aka runtime) UI. However, if you want to make UI that can be viewed in your Editor, you will have to use a different system. The two systems that can be used to create Editor UI are IMGUI, which we will discuss in the next chapter, and the UI Toolkit. However, while IMGUI can only be used to make Editor UI, the UI Toolkit can be used to make both runtime and Editor UI.
图 18.1:比较三个 UI 系统
Figure 18.1: Comparing the three UI systems
Unity 的目标是用新的 UI Toolkit 完全取代 uGUI 系统。然而,它仍处于开发阶段,并且具有 uGUI 的所有功能。例如,UI Toolkit 无法制作定位在 3D 世界中的 UI,就像我们在第 16 章中讨论的那样。它只能制作覆盖在屏幕顶部的 UI。此外,它不能使用自定义材质和着色器,并且不容易从 MonoBehaviours 中引用。
Unity’s goal is to replace the uGUI system entirely with the new UI Toolkit. However, it is still in development and does not have all the functionalities that uGUI does. For example, the UI Toolkit cannot make UI that is positioned in the 3D world, like what we discussed in Chapter 16. It can only make UI that overlays on top of the screen. Additionally, it cannot use custom materials and shaders and is not easily referenced from MonoBehaviours.
那么,您何时会想要使用 UI 工具包?如果您想创建风格化的 UI,而又不想处理预制件和预制件变体带来的臃肿问题,那么 UI 工具包将非常有用。您可以通过代码更改 UI 的设计,而不必更改 GameObject 的 Inspector 属性。这使得 UI 更容易在多个项目之间共享,并且可以快速自定义。此外,如果您有 Web 开发经验,您可能会更熟悉UI 工具包的工作流程。
So, when would you want to use the UI Toolkit? The UI Toolkit is extremely helpful if you want to create stylistic UI without dealing with the bloat that comes along with prefabs and prefab variants. You can change the design of UI through code instead of altering a GameObject’s Inspector properties. This makes UI more easily sharable across multiple projects and quickly customizable. Additionally, if you have experience with web development, you may be more comfortable with the workflow of the UI Toolkit.
如果您正在开发一个较旧的项目,则可能需要本书中讨论的有关 uGUI 的信息,因为它仍然是使用最广泛的 UI 系统。但是,如果您正在创建一个新项目并希望考虑使用 UI Toolkit 进行项目开发,我建议您您查看以下文档以确保此系统能够满足您的需求: https://docs.unity3d.com/Manual/UI-system-compare.xhtml
Odds are, if you are working on an older project, you will need the information discussed in this book that concentrates on uGUI as it is still the most widely used UI system. However, if you are creating a new project and would like to consider using the UI Toolkit for your project, I recommend you review the following documentation to ensure your needs will be met by this system: https://docs.unity3d.com/Manual/UI-system-compare.xhtml
现在我们已经了解了何时可以使用 UI 工具包,让我们开始将其安装到我们的项目中。
Now that we’ve reviewed when you can use the UI Toolkit, let’s get started with it by installing it in our project.
根据您使用的 Unity 版本,可能未安装必要的 UI Toolkit 包。版本所有相对较新的 Unity 版本都附带了可用于开发编辑器 UI 的 UI 工具包;但是,仅最新版本才附带允许您制作运行时 UI 的版本。
Depending on which version of Unity you are using, the necessary UI Toolkit packages may not be installed. The version of the UI Toolkit that allows you to develop Editor UI comes packaged with all relatively recent versions of Unity; however, the version that allows you to also make runtime UI only comes with the most recent versions.
您可以通过转到窗口| UI Toolkit来确定是否已安装 UI Toolkit 包的所有必要版本。如果UI Builder子菜单不存在或 UI Toolkit 菜单项完全缺失,则需要导入该包。
You can determine if you already have all necessary versions of the UI Toolkit package installed by going to Window | UI Toolkit. If the UI Builder submenu is not present or the UI Toolkit menu item is completely missing, you will need to import the package.
图 18.2:确定您是否安装了 UI Toolkit 包
Figure 18.2: Determining if you have the UI Toolkit package installed
如果需要安装 UI Toolkit,可以通过从git URL 导入包来安装。为此,请完成以下步骤:
If you need to install the UI Toolkit, you can do so by importing the package from the git URL. To do so, complete the following steps:
图 18.3:通过 git URL 添加包
Figure 18.3: Adding a package via the git URL
导入完成后,您应该在包窗口中看到以下工具包。
After the import finishes, you should see the following toolkit in your package window.
图 18.4:UI 工具包
Figure 18.4: The UI Toolkit package
导入完成后,您应该看到以下包。
After the import finishes, you should see the following package.
图 18.5:UI Builder 包
Figure 18.5: The UI Builder package
现在我们已经安装了适当的软件包,让我们看看 UI Toolkit 系统的各个部分。
Now that we have the appropriate packages installed, let’s look at the various parts of the UI Toolkit system.
UI 工具包使用一组资源和一个 GameObject 组件来创建 UI。用于创建 UI 的主要资源创建的UI如下:
The UI Toolkit uses a set of assets and a GameObject component to create UI. The primary assets used to create UI are as follows:
图 18.6:制作 UI Toolkit 界面时使用的资产
Figure 18.6: Assets used when making UI Toolkit interfaces
UI 文档是UXML 文件类型(扩展名为.uxml)。此文件使用Unity 可扩展标记语言( UXML ) 来定义UI 的布局和结构。虽然 UXML 是unity 特定标记语言,其工作方式与 HTML 和 XML 等其他标记语言类似。您可以通过右键单击 Asset 文件夹并选择Create | UI Toolkit | UI Document来创建 UXML 文件,然后为文件指定适当的名称。
A UI Document is a UXML file type (.uxml extension). This file uses Unity Extensible Markup Language (UXML) to define the layout and structure of the UI. While UXML is a unity-specific markup language, it works similarly to other markup languages like HTML and XML. You can create UXML files by right-clicking in an Asset folder and selecting Create | UI Toolkit | UI Document, then naming the file appropriately.
样式表是一种 USS 文件类型(.uss扩展名)。此文件用于指定可以在 UXML 文件中引用的样式属性。在使用 HTML 时,这与 CSS 文件非常相似。您可以通过右键单击 Asset 文件夹并选择Create | UI Toolkit | Style Sheet来创建 USS 文件,然后命名该文件适当。
A Style Sheet is a USS file type (.uss extension). This file is used to designate style properties that can be referenced in a UXML file. This works very similarly to a CSS file when working with HTML. You can create USS files by right-clicking in an Asset folder and selecting Create | UI Toolkit | Style Sheet, then naming the file appropriately.
面板设置资源(.asset扩展名)定义UI所具有的属性的集合。
A Panel Settings asset (.asset extension) defines the collection of properties that the UI will have.
图 18.7:默认面板设置资产的属性
Figure 18.7: A default Panel Settings asset’s properties
主题样式表是TSS文件类型(.tss扩展名)。它决定了哪些面板设置将与哪些 TSS 一起使用,以及USS 文件,通过维护它们的集合,如下图所示。
A Theme Style Sheet is a TSS file type (.tss extension). It determines which Panel Settings will be used with which TSS and USS files, by maintaining a collection of them, as shown in the following figure.
图 18.8: TSS 文件的检查器
Figure 18.8: The Inspector of a TSS file
当您创建面板时设置资产,一个名为Unity Themes的文件夹自动在同一文件夹中创建。在其中,将为您创建一个名为UnityDefaultRuntimeTheme.tss的 TSS 文件。但是,您可以通过右键单击 Asset 文件夹并选择创建| UI 工具包| TSS主题文件来创建更多 TSS 文件。
When you create a Panel Settings asset, a folder called Unity Themes is automatically created in the same folder. Within it, a single TSS file called UnityDefaultRuntimeTheme.tss will be created for you. However, you can create more TSS files by right-clicking in an Asset folder and selecting Create | UI Toolkit | TSS Theme File.
UI Toolkit 系统的最后一部分是UI Document组件,如下面的屏幕截图所示。
The final piece of the UI Toolkit system is the UI Document component, shown in the following screenshot.
图 18.9:UI 文档组件
Figure 18.9: The UI Document component
此组件会添加到场景中的 GameObject 中,并允许渲染使用 UI Toolkit 创建的 UI。它有三个属性:面板设置、源资产和排序顺序。
This component is added to a GameObject in your scene and allows your UI created with the UI Toolkit to be rendered. It has three properties: Panel Settings, Source Asset, and Sort Order.
您将面板设置资产分配给面板设置属性,以表示此组件渲染的 UI 将具有哪些设置。源资产属性是您分配 UI 文档 (UXML) 文件的地方。排序顺序属性决定了 UXML 文件定义的 UI 将以何种顺序呈现,相对于您在使用相同面板设置的场景。您可以使用Inspector中的Add Component并搜索UI Document将此组件添加到 GameObject 中。
You assign a Panel Setting asset to the Panel Settings property to signify which settings the UI rendered by this component will have. The Source Asset property is where you assign the UI Document (UXML) file. The Sort Order property determines what order the UI defined by the UXML file will render relative to any other UXML files you are rendering in the scene that use the same Panel Settings. You can add this component to a GameObject by using Add Component in the Inspector and searching for UI Document.
现在我们知道了所有必需的资产和组件,让我们回顾一下如何使用UI Builder 实际构建这些资产。
Now that we know all the required assets and components, let’s review how to actually build these assets using the UI Builder.
就像 uGUI 构建的 UI 由多个 GameObject 组成一样,UI Toolkit 构建的 UI 由Visual Elements组成。有UI Toolkit 系统中为您提供了多个 UI 元素(按钮、标签、滑块等),但 Visual Element 是所有 UI 元素的基类,因此它们的许多属性都从中派生出来。
In the same way that a uGUI-built UI is comprised of multiple GameObjects, a UI Toolkit-built UI is comprised of Visual Elements. There are multiple UI elements available to you in the UI Toolkit system (buttons, labels, sliders, etc.), but the Visual Element is the base class for all of them, so they all derive many of their properties from it.
正如游戏对象在 Unity 编辑器层级结构中组织一样,UI Toolkit 也以某种方式组织其视觉元素称为UI 层次结构。例如,假设我想创建一个类似于图 18 .10中显示的 UI 。
Just as GameObjects are organized in the Unity Editor Hierarchy, UI Toolkit organizes its Visual Elements in something called a UI Hierarchy. For example, let’s say I’d like to create a UI similar to the one displayed in Figure 18.10.
图 18.10:简单示例 UI
Figure 18.10: Simple sample UI
场景层次结构如果编辑器是用 uGUI 构建的,那么它与 UI Hierarchy 非常相似,如果它是用用UI Builder创建,如图18.11所示。
The Scene Hierarchy in the Editor, if it were built with uGUI, is quite similar to the UI Hierarchy, if it were created with UI Builder, as shown in Figure 18.11.
图 18.11:uGUI 的场景层次结构与 UI Toolkit 的 UI 层次结构
Figure 18.11: Scene Hierarchy for uGUI versus UI Hierarchy for UI Toolkit
除了命名和嵌套方面略有不同外,它们几乎完全相同。您可以考虑 UI 文档,它在 UI 层次结构中以Chapter18.uxml表示,其作用类似于场景层次结构中的 Canvas 功能。
Except for some slight changes in naming and nesting, they are almost the same. Where you can consider the UI Document, represented by Chapter18.uxml in the UI Hierarchy acting with a similar function to the Canvas in the Scene Hierarchy.
笔记
Note
UI 层次结构屏幕截图是从 UI Builder 工具获得的,我们将在下一节中讨论它。
The UI Hierarchy screenshot was obtained from the UI Builder tool, which we will discuss in the next section.
使用 uGUI 时,每个 GameObject 的对齐和位置都基于其嵌套的父对象。对于使用 UI Toolkit 系统创建的视觉元素也是如此。因此,了解项目应如何嵌套对于使用 UI Toolkit系统开发 UI 非常重要。
When working with uGUI, the alignment and position of each GameObject is based on the parent object it is nested under. This is true also for Visual Elements created with the UI Toolkit system. So, having a sense of how items should be nested is important for developing UI with the UI Toolkit system.
笔记
Note
您可以在代码包中提供的 Unity 项目中找到为图 18.10和图18.11开发的示例 UI 。它们可以在标记为Chapter18.asset的场景中找到。在场景层次结构中,Canvas GameObject 包含 uGUI 版本,而 UIDocument GameObject包含 UI Toolkit 版本。
You can find the example UI that was developed for Figure 18.10 and Figure 18.11 within the Unity project provided in the code bundle. They can be found within the scene labeled Chapter18.asset. In the Scene Hierarchy, the Canvas GameObject holds the uGUI version and the the UIDocument GameObject holds the UI Toolkit version.
现在我们已经回顾了 UI Toolkit 系统的一些基本概念,让我们看看如何实际使用它构建 UI。
Now that we’ve reviewed some basic concepts of the UI Toolkit system, let’s look at how we can actually build UI with it.
要使用 UI Toolkit 创建 UI,您需要必须创建一个 UI 文档来描述你将渲染哪些视觉元素以及它们的布局和其他属性。在 UI Toolkit 系统中创建 UI 文档的两种方法:
To create UI with the UI Toolkit, you have to create a UI Document to describe what Visual Elements you will render as well as their layout and other properties. There are two ways to create UI Documents within the UI Toolkit system:
您还可以将两者结合起来,在通过代码或UI Builder 编辑 UI 之间切换。
You can also do a combination of both and bounce between editing your UI via code or the UI Builder.
要访问 UI Builder,请选择窗口| UI 工具包| UI Builder。
To access the UI Builder, select Window | UI Toolkit | UI Builder.
图 18.12:UI Builder 窗口
Figure 18.12: The UI Builder window
在此窗口中,您可以将各种视觉元素从库(左下角)拖放到视口(中间)。您可以调整大小和还可以在此视口中移动视觉元素。您可以更改父级,从而更改视觉元素的对齐方式通过层次结构(左中)。此外,您还可以在UXML 审阅面板中查看从布局生成的代码文件,并在USS 预览面板中查看代表样式的代码文件(均位于底部中央)。本章末尾的示例部分提供了一个使用 UI Builder 构建 UI 的示例。
In this window, you can drag and drop various Visual Elements from the Library (bottom left) into the Viewport (center). You can resize and move the Visual Elements around in this Viewport, as well. You can change the parenting, thus changing how the Visual Element is aligned via the Hierarchy (left center). Additionally, you can also view the code file that is generated from your layout in the UXML Review Panel and the code files that represent your styles in the USS Preview Panel (both are in the bottom center). An example of walking through how to build a UI with the UI Builder is in the Examples section at the end of this chapter.
为了能够使用通过 UI Builder 创建的 UI 布局,您必须将 UI 文档保存为 .uxml文件。按Ctrl + S会将文件保存在您选择的位置。我建议您将它们保存在Assets中名为UI Toolkit的文件夹中 。
To be able to use the UI layout you’ve created with the UI Builder, you must save the UI Document as a .uxml file. Pressing Ctrl + S will save the file for you in a location of your choosing. I recommend you save them in a folder called UI Toolkit within Assets.
现在我们已经回顾了如何创建 UI 文档,让我们回顾一下如何将文档与游戏场景联系起来。
Now that we’ve reviewed how to create a UI Document, let’s review how to tie the document to your game’s scene.
您可以通过双击层次结构中的任意视觉元素并输入新名称来重命名它们。使用 UI 构建器时,如果需要,视觉元素的名称将充当变量通过 C# 代码引用它们。因此,赋予视觉元素独特的如果您计划通过 C# 代码访问它们,则可以使用名称(有关更多信息,请参阅使 UI 可通过事件交互部分)。
You can rename any of your Visual Elements by double-clicking on them in the Hierarchy and typing a new name. When using the UI Builder, the names of the Visual Elements will act as variables if you need to reference them by C# code. So, it’s important to give the Visual Elements distinct names if you plan to access them via C# code (see the Making the UI Interactable with Events section for further information).
正如本章UI 工具包系统各部分所述,UI 文档组件必须添加到场景中的 GameObject 中,以便渲染您创建的 UI。您可以通过将 UI Document 组件添加到现有 GameObject 或选择+ | UI Toolkit | UI Document来执行此操作。此 GameObject 将具有Transform组件和UI Document组件。
As mentioned in the Parts of the UI Toolkit System section of this chapter, the UI Document component must be added to a GameObject in your scene so that the UI you’ve created can be rendered. You can do this by either adding the UI Document component to an existing GameObject or selecting + | UI Toolkit | UI Document. This GameObject will have a Transform component and a UI Document component.
图 18.13: UIDocument 游戏对象的检查器
Figure 18.13: The Inspector of a UIDocument GameObject
如果您的项目Assets文件夹中还没有名为UI Toolkit的文件夹,系统将自动为您创建一个。在其中,您将找到一个PanelSettings资产和一个名为UnityThemes的文件夹。在UnityThemes中,您将找到一个名为UnityDefaultRuntimeTheme.tss的 TSS 文件。您的UIDocument GameObject 的UI Document组件将自动将PanelSettings资产分配给Panel Settings属性。但是,您需要将 UI Document 挂接到Source Asset属性中。将您所需的任何.umxl文件拖放到创建到Source Asset属性中,以查看您在场景中创建的 UI 。
If you do not already have a folder called UI Toolkit within your projects’s Assets folder, one will automatically be created for you. Within it, you will find a PanelSettings asset, and a folder called UnityThemes. Within UnityThemes, you will find a TSS file called UnityDefaultRuntimeTheme.tss. Your UIDocument GameObject’s UI Document component will automatically have the PanelSettings asset assigned to the Panel Settings property. However, you will need to hook your UI Document into the Source Asset property. Drag and drop whatever .umxl file you created into the Source Asset property to view the UI you created in your scene.
现在我们已经了解了构建和渲染 UI 的基础知识,让我们看看如何编写使用UI 工具包创建的 UI 交互。
Now that we know the basics of building and rendering our UI, let’s look at how to code the interactions of UI created with the UI Toolkit.
UI Builder 和你的UXML文档仅处理您的 UI。虽然您可以通过使用样式表分配一些基本响应(例如悬停时更改视觉效果),但您需要创建 C# 脚本来处理与您的 UI 相关的任何逻辑或事件。
The UI Builder and your UXML document only handle the visual properties of your UI. While you can assign some basic responses (like changing visuals on hover) through the use of style sheets, you will need to create C# scripts to handle any logic or events related to your UI.
能够编写与 UI 交互的代码文档,您必须使用UnityEngine.UIElements命名空间。如果您使用 UI Toolkit 制作编辑器UI,您可能还需要UnityEditor.UIElements命名空间。
To be able to write code that interfaces with UI Documents, you must use the UnityEngine.UIElements namespace. If you are using the UI Toolkit to make editor UI, you may also need the UnityEditor.UIElements namespace.
笔记
Note
有关UnityEngine.UIElements和UnityEditor.UIElements命名空间的更多信息,请参阅以下资源:https ://docs.unity3d.com/Packages/com.unity.ui@1.0/api/UnityEditor.UIElements.xhtml 。
For more information about the UnityEngine.UIElements and UnityEditor.UIElements namespaces, see the following resource: https://docs.unity3d.com/Packages/com.unity.ui@1.0/api/UnityEditor.UIElements.xhtml.
在您的 C# 脚本中,您可以创建一个UIDocument类型的引用变量。您可以像分配其他类的变量一样分配此变量:在Inspector中分配它、使用GetComponent、从另一个脚本传递对它的引用等等。
Within your C# script, you can create a reference variable to a UIDocument type. You can assign this variable in the same way you would generally assign a variable of another class: assigning it in the Inspector, using GetComponent, passing a reference to it from another script, and so on.
笔记
Note
虽然您可以使用FindObjectOfType ,但不建议用它来查找对UIDocument 的引用,因为您的场景中很可能有多个 UI 文档。
While you can use FindObjectOfType, this is not recommended for finding a reference to a UIDocument, as you will very likely have multiple UI Documents in your scene.
在获得要使用的UIDocument的引用后,您可以获得 Visual通过获取对 UI 文档上的根可视化元素的引用,然后查询该元素中的可视化元素,来获取其中的元素(例如按钮、标签等)按其名称输入。
After you get a reference to the UIDocument you want to work with, you can get a reference to the Visual Elements within it (e.g., the buttons, labels, etc.) by getting a reference to the root Visual Element on the UI Document, then Querying that element for the Visual Element type by its name.
例如,在本章前面展示的 UI 中,我将Label重命名为DogLabel,将Button重命名为CatButton,如下图所示:
For example, in the UI shown earlier in the chapter, I have renamed the Label to DogLabel and the Button to CatButton, as shown in the following figure:
图 18.14:标签和按钮的新名称
Figure 18.14: New names for Label and Button
为了在 C# 脚本中创建对这些变量的引用,我首先使用以下代码为UIDocument、Label和Button创建了引用变量:
To create a reference to these variables in a C# script, I first created reference variables for the UIDocument, the Label, and the Button, with the following code:
[SerializeField] 私有 UIDocument uiDocument; 自有品牌 dogLabel; 私人按钮 catButton;
[SerializeField] private UIDocument uiDocument; private Label dogLabel; private Button catButton;
uiDocument变量将在 Inspector 中分配。与所有MonoBehaviour脚本一样,此脚本需要附加到场景中的 GameObject。我将此脚本附加到UIDocument GameObject,然后将UIDocument GameObject 拖到Ui Document属性中。
The uiDocument variable will be assigned in the Inspector. As with all MonoBehaviour scripts, this script needed to be attached to a GameObject in the scene. I attached this script to the UIDocument GameObject and then dragged the UIDocument GameObject into the Ui Document property.
图 18.15:将 UIDocument 分配给脚本
Figure 18.15: Assigning the UIDocument to the script
值得注意的是,当您在 C# 中引用UIDocument ,则引用的是UIDocument组件,而不是 UI Document源文件。
It’s important to note that when you reference a UIDocument in C#, you are referencing the UIDocument component, not a UI Document source file.
为了初始化dogLabel和catButton ,我获取了对uiDocument上的rootVisualElement的引用,然后使用Query方法查找每个Visual Element 。
To initialize dogLabel and catButton, I got a reference to the rootVisualElement on the uiDocument, then used the Query method to find each Visual Element.
无效开始(){
var root = uiDocument.rootVisualElement;
dogLabel = root.Q<Label>("DogLabel");
catButton = root.Query<Button>("CatButton");
}void Start(){
var root = uiDocument.rootVisualElement;
dogLabel = root.Q<Label>("DogLabel");
catButton = root.Query<Button>("CatButton");
} 注意我如何使用root.Q来查找Label并使用root.Query来查找Button 。您可以使用其中任何一种,我这样做只是为了让您看到有两种不同的方法可以做到这一点。使用哪一种取决于您的偏好。
Notice how I used root.Q to find the Label and root.Query to find the Button. You can use either one, and I only did this so you can see that there are two different ways to do it. It is up to your preference which one you use.
由于UXML文档不管理事件,因此您可以了解UXML文档中定义的UI是否与通过订阅事件或在事件触发时注册回调方法。
Since the UXML document does not manage events, you can find out if the UI defined in the UXML document is interacted with by either subscribing to an event or registering a callback method when the event is triggered.
例如,如果我想在单击catButton时在控制台中记录一条消息,我需要创建一个订阅 catButton的单击事件的方法。
For example, if I want to log a message in the Console when the catButton is clicked, I need to create a method that is subscribed to the catButton’s clicked event.
首先,我在脚本中添加以下方法:
First, I add the following method to my script:
私有 void OnCatButtonClicked() {
Debug.Log("CatButtonClicked");
}private void OnCatButtonClicked() {
Debug.Log("CatButtonClicked");
} 然后,我需要订阅catButton的点击事件,在Start()方法中使用以下内容。
Then, I need to subscribe to the catButton’s clicked event, with the following in the Start() method.
catButton.clicked += OnCatButtonClicked;
catButton.clicked += OnCatButtonClicked;
为了避免在 GameObject 被禁用或销毁时事件订阅出现任何错误,我使用OnDisable()方法取消订阅该事件。
To avoid any errors with the event subscription when the GameObject is disabled or destroyed, I unsubscribe from the event in the OnDisable() method.
私有无效OnDisable(){
catButton.clicked-=OnCatButtonClicked;
}private void OnDisable() {
catButton.clicked -= OnCatButtonClicked;
} 这样,每当您单击按钮时,控制台中就会显示OnCatButtonClicked消息。
This causes the OnCatButtonClicked message to display in the Console whenever you click the button.
类似于 uGUI GameObjects 的方式它们的属性可以通过 C# 代码更改,Visual Element 的属性也可以通过 C# 代码调整。例如,将OnCatButtonClicked()方法更改为以下内容将使单词Text更改为Meow!
Similar to the way uGUI GameObjects can have their properties changed via C# code, a Visual Element can have its properties adjusted via C# code, as well. For example, changing the OnCatButtonClicked() method to the following will make the word Text change to Meow!
公共相机theCamera;
无效更新()
{
变换。查看(2 * 变换。位置 - 相机。变换。位置);
}public Camera theCamera;
void Update()
{
transform.LookAt(2 * transform.position - theCamera.transform.position);
} 上述代码使 UI 显示如下:
The preceding code causes the UI to appear as follows:
图 18.16:点击按钮后文本发生改变的 UI
Figure 18.16: The UI after the text changes via a button click
我还有很多可以说很多关于如何使用 UI Toolkit 的内容,但正如我在本章开头所说的那样,我只能给出一个总体概述——否则,我就得单独写一本关于 UI Toolkit 的书了!不过,我相信我已经对重要概念进行了足够的概述,以便您可以深入研究下一节中的示例。
There is a lot more I could say about working with the UI Toolkit, but as I said at the beginning of this chapter, I could only give a general overview—otherwise, I’d have to write a whole separate book just on the UI Toolkit! However, I believe I’ve given enough of an overview of the important concepts so that you can dive into working with the examples in the next section.
现在我们已经掌握了基本构建块,我们需要开始使用 UI Toolkit 实际创建一些 UI。我没有深入研究样式表或各种视觉元素的属性,但我将在这里展示一些示例来扩展这些内容。如果在完成这些示例后,您想了解更多信息,请参阅资源部分,获取大量示例和文档。
Now that we have the basic building blocks we need to get started with actually creating some UI with UI Toolkit. I didn’t dive deep into style sheets or the properties of the various Visual Elements, but I’ll show some examples that will expand upon that here. If after completing these examples, you want to learn more, see the Resources section for a bunch of examples and documentation.
When using the UI Toolkit to develop UI, there are two main parts to the work:
因此,每个示例将分为两部分:一部分用于构建 UI,另一部分用于编写功能。
So, each of these examples will be broken into two parts: one for building the UI and the other for writing the functionality.
由于 UI 工具包可以用于编辑器和运行时 UI,我将向您展示两者的一个示例。让我们从编辑器示例开始!
Because the UI Toolkit can be used for both Editor and runtime UI, I’ll show you one example of both. Let’s start with the Editor example!
虽然本书主要关注运行时 UI,但我没有向您展示如何使用 UI Toolkit 制作一些编辑器 UI,因为这是它的主要优点之一。编辑器 UI 通常用于工具来促进开发、提高生产力、简化工作流程等等。最初,我计划向您展示如何制作一些通用的编辑器工具,您可以扩展这些工具来改善您的工作流程。然而,我决定制作一个可以改善您心情的编辑器工具。毕竟,自我照顾对生产力至关重要!虽然这个例子在传统意义上可能没有任何实际用途,但它很有趣,它向您展示了如何使用许多编辑器 UI 功能,所以这是一个双赢的结果。
While the primary focus of this book is on runtime UI, I would be remiss to show you how to use the UI Toolkit to make some Editor UI, since that’s one of its main benefits. Editor UI is usually used for tools to facilitate development, improve productivity, streamline your workflow, and so on. Originally, I planned to show you how to make some generic Editor tools that you could expand to improve your workflow. However, I decided instead to make an Editor tool that can improve your mood. Self-care is essential to productivity, after all! While this example might not have any practical purposes in the traditional sense, it’s fun and it shows you how to use lots of Editor UI features, so it’s a win-win.
本示例介绍如何制作一个虚拟宠物,它会在编辑器窗口中陪伴您,并在您点击它时向您致意。本示例还演示了如何在Unity 编辑器中创建动画精灵表。
This example covers how to make a virtual pet that hangs out with you in a window in your Editor and compliments you when you click on it. This also demonstrates how to create animated sprite sheets within the Unity Editor.
图 18.17:本例中创建的编辑器虚拟宠物
Figure 18.17: The Editor virtual pet created in this example
本例中使用的猫精灵来自此处提供的公共领域艺术资产: https: //opengameart.org/content/a-cat。
The cat sprite used in this example is derived from the public domain art asset provided here: https://opengameart.org/content/a-cat.
笔记
Note
对于此示例,由于我们正在制作一个与我们迄今为止所做的任何工作都不相关的编辑器工具,因此您可能需要创建一个新的 Unity 项目来完成这项工作。如果这样做,您将需要重新导入任何 2D UI 包。
For this example, since we are making an Editor Tool that is not related to any of the work we’ve done so far, you might want to create a new Unity project to complete this work in. If you do, you will need to reimport any 2D UI packages.
让我们从布局开始使用 UI Builder 来创建 UI !
Let’s start with laying out the UI with the UI Builder!
要创建如图 18.17所示的虚拟宠物,请完成以下步骤:
To create the virtual pet shown in Figure 18.17, complete the following steps:
图 18.18:模糊的精灵表
Figure 18.18: The blurry sprite sheet
图 18.19:切片精灵表
Figure 18.19: The sliced sprite sheet
图 18.20:UXML 文件的检查器
Figure 18.20: The UXML file’s Inspector
图 18.21:添加按钮
Figure 18.21: Adding a Button
图 18.22:将背景图像类型更改为 Sprite
Figure 18.22: Changing the Background Image type to Sprite
图 18.23:应用了猫图像的按钮
Figure 18.23: The Button with the cat image applied
图 18.24:从按钮中删除文本
Figure 18.24: Removing the Text from the button
图 18.25:更改按钮大小
Figure 18.25: Changing the button size
图 18.26:向层次结构添加视觉元素
Figure 18.26: Adding a Visual Element to the Hierarchy
图 18.27:使按钮成为 VisualElement 的子元素
Figure 18.27: Making the Button a child of VisualElement
图 18.28:更新 Grow 属性
Figure 18.28: Updating the Grow property
图 18.29:调整 VisualElement 的 Align 属性
Figure 18.29: Adjusting the Align properties of the VisualElement
图 18.30:调整 VisualElement 的背景颜色
Figure 18.30: Adjusting the VisualElement’s background color
图 18.31:删除按钮的背景颜色
Figure 18.31: Removing the Button’s background color
图 18.32:最终的 UI 外观
Figure 18.32: The final UI look
图 18.33:具有适当名称的 IdleCat.uxml 层次结构
Figure 18.33: The IdleCat.uxml Hierarchy with appropriate names
笔记
Note
如果将UIDocument GameObject 添加到当前打开的场景中,您可能会看到有关控制台中缺少面板设置的警告。您可以删除此 GameObject 并关闭错误消息。
If a UIDocument GameObject was added to your currently open scene, you may see a warning about a Panel Setting missing in your Console. You can just delete this GameObject and dismiss the error message.
现在我们的 UI 已经完全布局好了,我们可以编写代码来开始添加功能!
Now that our UI is fully laid out, we can write the code to start adding in the functionality!
让我们的猫出现在我们的Unity Editor 并具有功能,我们需要编写一些 C# Editor 代码。我们希望猫能做以下几件事:
To get our cat to appear as a window in our Unity Editor and have functionality, we need to write some C# Editor code. There are a few things we want the cat to do:
我们可以用一个 C# 脚本实现所有这些。要让您的虚拟宠物出现在 Unity 编辑器的窗口中,请完成以下步骤:
We can achieve all of this in one C# script. To have your virtual pet appear in a window within your Unity Editor, complete the following steps:
公共类 IdleCat:EditorWindow {这将自动添加UnityEditor命名空间。
public class IdleCat : EditorWindow {This will automatically add the UnityEditor namespace.
[MenuItem("工具/我很孤独_%#K")]
公共静态void ShowIdleCat(){
EditorWindow 窗口 = GetWindow<IdleCat>();
窗口.titleContent = 新的GUIContent (“Kitty”);
}在上述代码中,[MenuItem("Tools/I'm Lonely _%#K")]行创建Tools | I'm Lonely菜单项,并在选择该菜单项时运行ShowIdleCat()方法。_ %#K表示也可以使用Ctrl + Shift + K快捷键来实现此目的。
以下行实例化窗口并将其标题设置为Kitty:
EditorWindow 窗口 = GetWindow<IdleCat>(); 窗口.titleContent = 新的GUIContent (“Kitty”);
保存你的代码,然后你应该会在编辑器中看到菜单项。单击它或键入Ctrl + Shift + K应该会打开一个名为Kitty 的窗口。
[MenuItem("Tools/I'm Lonely _%#K")]
public static void ShowIdleCat() {
EditorWindow window = GetWindow<IdleCat>();
window.titleContent = new GUIContent("Kitty");
}In the preceding code, the [MenuItem("Tools/I'm Lonely _%#K")] line creates the Tools | I’m Lonely menu item and runs the ShowIdleCat() method when it is selected. _%#K signifies that this can also be achieved with the Ctrl + Shift + K shortcut.
The following lines instantiate the window and set its title to Kitty:
EditorWindow window = GetWindow<IdleCat>();
window.titleContent = new GUIContent("Kitty");Save your code and you should see the menu item in your Editor. Clicking it or typing Ctrl + Shift + K should bring up a window named Kitty.
图 18.34:工具菜单和 Kitty 窗口
Figure 18.34: The Tools menu and the Kitty window
窗口.maxSize = 新 Vector2(100,100); 窗口.最小尺寸 = 窗口.最大尺寸;
这不会自动更改当前打开的窗口的大小,因为此功能仅在您使用菜单项或快捷键时运行。现在,您可以通过执行其中一个操作来调整窗口大小。您无需关闭窗口。您可以在窗口仍打开时执行此操作。
需要注意的一点是,编辑器窗口可以停靠在整个 Unity 编辑器中。因此,窗口的大小可以根据用户停靠的位置而变化。
window.maxSize = new Vector2(100, 100); window.minSize = window.maxSize;
This will not automatically change the size of your currently open window, because this function only runs when you use the menu item or the shortcut keys. You can now resize your window by doing one of those actions now. You do not need to close the window. You can do this while it is still open.
One thing to note is that Editor Windows can be docked throughout the Unity Editor. So, the size of your window can change based on where the user docks it.
使用编辑器 UI 时,CreateGUI()方法是用于初始化UI 的事件函数。
编写以下代码,确保将UnityEngine.UIElements命名空间添加到脚本顶部:
私有无效CreateGUI(){
var 根 = rootVisualElement;
var quickToolVisualTree = Resources.Load<VisualTreeAsset>("IdleCat");
quickToolVisualTree.CloneTree(根);
}此代码块执行了几项操作。首先,它获取我们创建的编辑器窗口的rootVisualElement 。然后,它通过在Editor/Resources文件夹中搜索名为IdleCat的VisualTreeAsset来找到IdleCat.uxml文件。然后,它克隆IdleCat.uxml UI 文档并将其放置在窗口的根目录中。
如果您返回编辑器,您现在应该可以在打开的窗口的粉红色背景上看到您的小猫。
When working with Editor UI, the CreateGUI() method is an Event Function used to initialize the UI.
Write the following code, making sure to add the UnityEngine.UIElements namespace to the top of the script:
private void CreateGUI() {
var root = rootVisualElement;
var quickToolVisualTree = Resources.Load<VisualTreeAsset>("IdleCat");
quickToolVisualTree.CloneTree(root);
}This code block does a few things. First, it gets the rootVisualElement of the Editor Window we have created. Then, it finds the IdleCat.uxml file by searching for a VisualTreeAsset named IdleCat within the Editor/Resources folder. Then, it clones the IdleCat.uxml UI Document and places it within the window’s root.
If you return to the Editor, you should now see your Kitty on the pink background in your open window.
图 18.35:具有适当大小和 UI 文档的窗口
Figure 18.35: The window with the appropriate size and UI Document
私人按钮 catButton;
private Button catButton;
catButton = root.Q<Button>("CatButton");catButton = root.Q<Button>("CatButton");catButton.tooltip = "喵";
保存脚本后,您应该能够立即在编辑器中看到这些更改。(请注意,我的屏幕截图工具不会捕获鼠标光标,因此下图中未显示。)
catButton.tooltip = "meow";
After you save the script, you should be able to immediately see these changes reflected in your Editor. (Note that my screenshot tool doesn’t capture the mouse cursor, so it is not shown in the following figure.)
图 18.36: 喵喵提示
Figure 18.36: The meow tooltip
私有无效OnCatButtonClicked()
{
Debug.Log("你做得很好!");
}private void OnCatButtonClicked()
{
Debug.Log("You're doing great!");
}catButton.clicked += OnCatButtonClicked;
另外,创建以下OnDisable()方法:
私有无效OnDisable()
{
catButton.clicked-=OnCatButtonClicked;
}一旦保存,当您单击它时,您就可以在控制台中看到猫对您表示称赞。
catButton.clicked += OnCatButtonClicked;
Also, create the following OnDisable() method:
private void OnDisable()
{
catButton.clicked -= OnCatButtonClicked;
}Once you save, you can already see the cat compliment you in the Console when you click on it.
图 18.37:赞美的猫
Figure 18.37: The complimenting cat
实现这一点的一种方法是使用协程,但是不幸的是,协程在编辑器中默认不起作用。但是,我们可以通过从Unity 导入编辑器协程包来在我们的编辑器中使用协程。
为此,请选择“窗口” | “包管理器”以打开包管理器。
One way to do this is with coroutines, but unfortunately, coroutines do not work by default in the Editor. However, we can use coroutines in our Editor by importing the Editor Coroutines package from Unity.
To do that, select Window | Package Manager to open the Package Manager.
图 18.38:更改包管理器中显示的包
Figure 18.38: Changing which packages appear in the Package Manager
图 18.39:解锁编辑器协程包
Figure 18.39: Unlocking the Editor Coroutines package
使用 Unity.EditorCoroutines.Editor;
现在,我们可以在代码中使用EditorCoroutines了!它们的工作原理与常规协程非常相似。但是,在编写第一个 Editor 协程来控制动画之前,让我们先访问我们想要在动画中使用的图像并编写一些辅助函数。
using Unity.EditorCoroutines.Editor;
Now, we can use EditorCoroutines in our code! They work pretty similarly to regular coroutines. But, before we write our first Editor coroutine to control our animations, let’s get access to the images we’ll want to use in our animation and write some helper functions.
私有 List<StyleBackground> idleBackgrounds = new List<StyleBackground>(); 私有列表 <StyleBackground> pettingBackgrounds = 新列表 <StyleBackground>();
private List<StyleBackground> idleBackgrounds = new List<StyleBackground>(); private List<StyleBackground> pettingBackgrounds = new List<StyleBackground>();
Sprite[] allSprites = Resources.LoadAll<Sprite>("idleCat");这将查找Editor/Resources文件夹并将所有名为 idleCat 的精灵存储到allSprites数组中。
Sprite[] allSprites = Resources.LoadAll<Sprite>("idleCat");This will look in the Editor/Resources folder and store all of the sprites with the name idleCat to the allSprites array.
图 18.40: 哪些精灵进入哪个动画
现在,我们需要循环遍历allSprites数组并将这些精灵分配到正确的列表中。我们可以通过在CreateGUI()方法中添加以下代码来实现:
对于(int i = 0; i <= allSprites.Length - 1; i++)
{
StyleBackground backgroundImage = new StyleBackground(allSprites[i]);
如果 (i < 11)
{
抚摸背景.添加(背景图像);
}
如果 (i >= 10)
{
空闲背景.添加(背景图像);
}
} Figure 18.40: Which sprites go to which animation
Now, we need to loop through the allSprites array and divvy those sprites up into the correct list. We can do so by adding the following code to the CreateGUI() method:
for (int i = 0; i <= allSprites.Length - 1; i++)
{
StyleBackground backgroundImage = new StyleBackground(allSprites[i]);
if (i < 11)
{
pettingBackgrounds.Add(backgroundImage);
}
if (i >= 10)
{
idleBackgrounds.Add(backgroundImage);
}
} 私有 int 动画索引 = 0;
private int animationIndex = 0;
私有无效空闲动画()
{
动画索引++;
如果(动画索引 >= idleBackgrounds.Count)
{
动画索引 = 0;
}
catButton.style.backgroundImage = 空闲背景[动画索引];
}请注意,当索引超出范围时,它会循环回到0。
private void IdleAnimation()
{
animationIndex++;
if (animationIndex >= idleBackgrounds.Count)
{
animationIndex = 0;
}
catButton.style.backgroundImage = idleBackgrounds[animationIndex];
}Notice that when the index is out of range, it loops back around to 0.
私有无效PettingAnimation()
{
动画索引++;
如果(animationIndex> = pettingBackgrounds.Count)
{
动画索引 = 0;
}
catButton.style.backgroundImage = pettingBackgrounds[animationIndex];
}private void PettingAnimation()
{
animationIndex++;
if (animationIndex >= pettingBackgrounds.Count)
{
animationIndex = 0;
}
catButton.style.backgroundImage = pettingBackgrounds[animationIndex];
}笔记
Note
IdleAnimation ()和PettingAnimation()方法有一些可重用的代码,可以重构以简洁;但是,为了清楚起见,我将保持原样。
The IdleAnimation() and PettingAnimation() methods have some reusable code and could be refactored for brevity; however, for clarity, I will keep it the way it is.
IEnumerator NextAnimationFrame()
{
var waitForOneSecond = new EditorWaitForSeconds(1f);
while(真)
{
产生返回 waitForOneSecond;
空闲动画();
}
}这将创建一个无限循环,每秒运行一次IdleAnimation()方法。
IEnumerator NextAnimationFrame()
{
var waitForOneSecond = new EditorWaitForSeconds(1f);
while (true)
{
yield return waitForOneSecond;
IdleAnimation();
}
}This will create an infinite loop that runs the IdleAnimation() method every second.
EditorCoroutineUtility.StartCoroutine(NextAnimationFrame(),this);
EditorCoroutineUtility.StartCoroutine(NextAnimationFrame(), this);
私人 bool 空闲 = true;
这个变量将跟踪猫是否应该空转。
private bool idle = true;
This variable will keep track of whether or not the cat should be idling.
空闲=假;
idle = false;
IEnumerator NextAnimationFrame()
{
var waitForOneSecond = new EditorWaitForSeconds(1f);
while(真)
{
产生返回 waitForOneSecond;
如果(空闲)
{
空闲动画();
}
别的
{
抚摸动画();
}
}
}IEnumerator NextAnimationFrame()
{
var waitForOneSecond = new EditorWaitForSeconds(1f);
while (true)
{
yield return waitForOneSecond;
if (idle)
{
IdleAnimation();
}
else
{
PettingAnimation();
}
}
}私有 bool windowOpen = true;
private bool windowOpen = true;
while (窗口打开) {while (windowOpen) {窗口打开=false;
windowOpen = false;
就是这样!现在你应该有一只小猫咪朋友,在你工作时陪你玩。每当你需要一点提神剂时,点击她。
That’s it! You should now have a little kitty friend that hangs out with you while you work. Click on her whenever you need a little pick-me-up.
使用 UI Toolkit 制作带有样式表和动画过渡的菜单
Using the UI Toolkit to make a menu with style sheets and animation transitions
我们将了解如何使用 UI Builder创建一个使用样式表和过渡动画的基本菜单,然后我们将连接菜单中的按钮来访问网络数据并在此示例中随机生成内容。
We’ll look at how to use the UI Builder to create a basic menu that uses style sheets and transition animations, then we’ll hook up buttons within the menu to access web data and randomly generate content in this example.
由于上一个示例是一只旨在为您带来快乐的虚拟宠物,因此我决定在本示例中也坚持“自我照顾”的主题。下图是我们将要制作的 UI 的屏幕截图使用随机生成的猫图像和从网络上检索到的引语。
Since the last example was a virtual pet meant to bring you happiness, I decided to stick with the theme of “self-care” for this example, as well. The following figure is a screenshot of the UI we will make with a randomly generated image of a cat and a quote retrieved from the web.
图 18.41:具有随机生成内容的 UI 菜单
Figure 18.41: The UI menu with randomly generated content
按下Charm Me按钮将随机生成一张猫图片,按下Inspire Me按钮将随机生成一句励志名言。此外,当鼠标悬停在按钮上并点击按钮时,按钮会改变颜色,同时动画显示为稍大的形状。
Pressing the Charm Me button will randomly generate a cat picture and pressing the Inspire Me button will randomly generate an inspirational quote. Additionally, the buttons change color when hovered over and clicked on while animating to a slightly bigger shape.
与前面的示例一样,我们将首先使用 UI Builder 布置 UI 。
As with the previous example, we will start by laying out the UI with the UI Builder.
要创建如图 18.41所示的 UI ,请完成以下步骤:
To create the UI shown in Figure 18.41, complete the following steps:
如果你还没有有一个,这个将自动创建一个名为UI Toolkit的新Assets文件夹。它将包含一个PanelSettings资源和一个Unity Themes文件夹。
If you don’t already have one, this will automatically create a new Assets folder called UI Toolkit. It will contain a PanelSettings asset and a Unity Themes folder.
图 18.42:设置游戏视图分辨率
Figure 18.42: Setting your Game view resolution
图 18.43:设置画布大小以匹配游戏视图
Figure 18.43: Setting the Canvas Size to Match Game View
图 18.44:在视口中调整大小的按钮元素
Figure 18.44: The Button element resized in the Viewport
图 18.45:如何更改按钮上显示的文本
Figure 18.45: How to change the text that displays on the button
图 18.46:按钮的文本设置
Figure 18.46: The Text settings of Button
图 18.47:更改按钮的背景颜色
Figure 18.47: Changing the background color of the button
图 18.48:调整边框属性
Figure 18.48: Adjusting the Border properties
图 18.49:UI Builder Viewport 和 Editor Game View 之间的区别
为了确保什么你看到的是UI Builder 与您的游戏视图匹配,从 UI Builder右上角的下拉菜单中选择Unity 默认运行时主题视口。
图 18.50:将视口设置为 Unity 默认运行时主题
Figure 18.49: The difference between the UI Builder Viewport and Editor Game View
To make sure what you are seeing in the UI Builder matches your Game view, select Unity Default Runtime Theme from the dropdown in the top-right corner of the UI Builder Viewport.
Figure 18.50: Setting the Viewport to Unity Default Runtime Theme
图 18.51:更改文本颜色
Figure 18.51: Changing the Text color
图 18.52:调整 VisualElement 的大小
Figure 18.52: Resizing the VisualElement
图 18.53:面板及其新设置
Figure 18.53: The Panel with its new settings
图 18.54:将按钮设置为 VisualElement 的父级
Figure 18.54: Parenting the Button to the VisualElement
图 18.55:添加新的 VisualElement
Figure 18.55: Adding in a new VisualElement
图 18.56:调整 VisualElement 的大小
Figure 18.56: Resizing the VisualElement
图 18.57:向层次结构添加另一个 VisualElement
Figure 18.57: Adding another VisualElement to the Hierarchy
图 18.58:将 Flex Direction 设置为 row
这样做会导致它的两个VisualElement子元素从左到右排列,而不是从上到下排列。
Figure 18.58: Setting the Flex Direction to row
Doing this will cause its two VisualElement children to line up left to right rather than top to bottom.
图 18.59:当前布局的层次结构
Figure 18.59: The Hierarchy of our current layout
图 18.60:在背景上设置背景图像
Figure 18.60: Setting the background image on the background
图 18.61:添加新按钮
Figure 18.61: Adding a new Button
我们可以通过在文本框中输入名称,然后选择“将内联样式提取到新类”直接从其属性中创建样式表。
在文本框中输入button-class,然后按将内联样式提取到新类按钮。
We can create a style sheet directly from its properties by putting a name within the textbox and then selecting Extract Inlined Styles to New Class.
Type button-class into the textbox and then press the Extract Inlined Styles to New Class button.
图 18.62:使用“提取内联样式到新类”创建样式表
Figure 18.62: Creating a style sheet with Extract Inlined Styles to New Class
图 18.63:新的样式表
现在,您可以将此样式应用于未设置样式的按钮,方法是将ButtonStyle.uss从StyleSheet窗口拖到Button上。现在,这两个按钮将具有相同的属性。
图 18.64:两个具有相同样式表的按钮
Figure 18.63: The new style sheet
You can now apply this style to the unstyled button by dragging ButtonStyle.uss from the StyleSheet window onto the Button. Now, the two Buttons will have the same properties.
Figure 18.64: The two buttons with the same style sheet
您的 UI Builder 应如下所示:
Your UI Builder should look like the following:
图 18.65: 按钮位于其 VisualElement 父级的中心
Figure 18.65: The Buttons centered in their VisualElement parent
图 18.66:手动定位 VIsualElement
Figure 18.66: Manually positioning the VIsualElement
图 18.67:向 UI 添加标签
Figure 18.67: Adding a Label to the UI
图 18.68:标签的 Text 属性
Figure 18.68: The Text property of the Label
图 18.69:使用视口中的预览查看按钮样式的变化
当您将鼠标悬停并单击按钮时,按钮会变得更暗更大。
Figure 18.69: Using Preview in the Viewport to see the Buttons style changes
The buttons now get darker and larger when you hover and click on them.
从StyleSheet面板中选择.button-class样式。在其检查器中,展开Transition Animation属性。我们希望它在缩放时进行动画处理。因此,使用下拉菜单将Property从all更改为width 。此外,将Duration设置为0.5,将Easing设置为EaseIn。这表示当按钮改变宽度时,它将在 0.5 秒内完成并慢慢进入。
Select the .button-class style from the StyleSheet Panel. Within its Inspector, expand the Transition Animation property. We want it to animate when it scales. So, change the Property from all to width using the dropdown. Also, set the Duration to 0.5 and the Easing to EaseIn. This indicates that when the button changes width, it will do so over 0.5 seconds and will ease into it.
图 18.70: 宽度过渡动画
Figure 18.70: The width transition animation
图 18.71:按钮的过渡动画
播放预览来观察鼠标悬停在按钮上时按钮逐渐变大的情况。
Figure 18.71: The Buttons’ transition animations
Play the Preview to watch the buttons grow gradually as you hover over them.
图 18.72:重命名我们想要通过代码访问的视觉元素
Figure 18.72: Renaming the Visual Elements we’ll want to access via code
呼。步骤好多啊!但是我们正式完成了 UI Builder。我们可以现在致力于我们的 C# 脚本来获取我们想要的功能。
Whew. That was a lot of steps! But we are officially done with the UI Builder. We can now work on our C# scripts to get the functionality we want.
目前,我们的按钮不执行任何操作但当我们将鼠标悬停在它们上面或单击它们时,它们会动起来。我们现在需要将它们与某些功能联系起来。我们对这两个按钮单击时的目标如下:
Currently, our Buttons do not do anything but animate when we hover over them or click them. We now need to hook them up to some functionality. Our goals for the two Buttons when clicked are as follows:
老实说,到目前为止,我们为这个示例所做的工作就是我最初开始计划时想要涵盖的全部内容。但是,我对随机生成 VisualElements属性有点兴奋,可能使示例变得有点太复杂了。毕竟,我们工程师喜欢过度设计。此示例使用了 Web 请求和 JSON 操作的概念。由于 Web 开发不是本书的重点,因此我不会详细讨论执行这些功能的代码。我希望此示例的重点放在特定于 UI 的代码上,而不是 Web 请求上。我将解释每段代码的作用,但我不一定会逐行解释。
Honestly, what we’ve done up to this point for this example was all I wanted to cover when I initially started planning it. However, I got a bit excited about randomly generating the VisualElements properties and I possibly made the example a bit too complicated. We engineers love to over-engineer, after all. This example uses concepts of web requests and JSON manipulation. Since web development is not a focus of this book, I won’t belabor the code that performs these functions. I want the focus of this example to be on the UI-specific code, not the web requests. I will explain what each section of code does, but I will not necessarily explain it line-by-line.
在开始实际的代码之前,我想简要介绍一下我们将从中获取数据的两个来源。我们将分别使用以下资源来获取猫图片和励志名言:
Before we get to the actual code, I do want to give a brief rundown of the two sources we will be getting our data from. We will use the following resources to get cat pictures and inspirational quotes, respectively:
The Place Kitten 网站允许您只需在 URL 末尾添加尺寸即可获得具有特定尺寸的小猫图片。例如,https ://placekitten.com/300/300显示一张 300 x 300 的小猫图片。当您开发网站时,只需用一些东西填充特定位置,此网站非常适合添加占位符图像。而且,它很可爱!
The Place Kitten website allows you to get a picture of a kitten with a specific dimension by simply adding the dimension to the end of the URL. For example, https://placekitten.com/300/300 displays an image of a kitten that is 300 x 300. This website is great for adding placeholder images when you are developing websites and just need something to fill a specific place. Plus, it’s cute!
Zen Quotes 网站托管了一个 API,允许您获取励志名言。API 是一组允许您与其数据交互的函数。API 具有哪些类型的函数将取决于 API 的用途以及工程师选择创建它的设计范例。但通常,API 将具有获取数据的能力。Zen Quotes 允许您获取励志名言。您可以获取单个名言或一整套名言。访问他们的网站,查看它还有哪些其他选项可用于从其数据库中检索数据。Zen Quotes 以 JSON 格式返回您从其请求的数据。JSON 是一种提供信息的格式标准化。当您获取它时,它将如下所示:
The Zen Quotes website hosts an API that allows you to get inspirational quotes. An API is a collection of functions that let you interact with its data. What types of functions the API has will depend on the use of the API and the design paradigm the engineers chose to create it. But generally, an API will have the ability to GET data. Zen Quotes allows you to GET inspirational quotes. You can GET a single quote or a whole set of them. Visit their site to see what other options it has for retrieving data from its database. Zen Quotes returns the data you request from it in JSON format. JSON is a format standardization that provides information. It will look like the following when you get it:
{“string”:“HelloWorld”,“boolean”:“false”,“number”:“123”,“array”:“[1,2,3]”,“object”:“prop1”:“a”,“prob2”:“b”}}{"string":"HelloWorld","boolean":false,"number":123,"array":[1,2,3],"object":{"prop1":"a","prob2":"b"}} 但它可以格式化为如下所示:
But it can be formatted to look like the following:
{
“字符串”:“HelloWorld”,
“布尔值”:false,
“数字”:123,
“大批”: [
1、
2、
3
],
“目的”: {
“prop1”:“a”,
“prob2”:“b”
}
}{
"string": "HelloWorld",
"boolean": false,
"number": 123,
"array": [
1,
2,
3
],
"object": {
"prop1": "a",
"prob2": "b"
}
} 我不会详细解释上述代码的含义,但我会指出该示例的重要细节我们会介绍。如果您想了解有关 JSON 格式的更多信息,请参阅以下资源: https: //www.w3schools.com/js/js_json_intro.asp
I won’t get into the specifics of what the preceding code means, but I will point out the important details of the example we cover. If you’d like to learn more about JSON format, see the following resource: https://www.w3schools.com/js/js_json_intro.asp
要完成此示例并让按钮生成猫图片和励志名言,请完成以下步骤:
To finish out this example and have the buttons generate cat pictures and inspirational quotes, complete the following steps:
私人UIDocument uiDocument;
无效开始()
{
uiDocument = 获取组件<UIDocument>();
}在我们的 Editor 代码示例之后,此代码应该看起来很熟悉;主要区别在于我们使用GetComponent获得了对UIDocument的引用。因此,我们需要将此脚本放在包含我们的 UI Document 组件的同一 GameObject 上。
private UIDocument uiDocument;
void Start()
{
uiDocument = GetComponent<UIDocument>();
}This code should look familiar after our Editor code example; the main difference is we got the reference to the UIDocument by using GetComponent. So, we’ll need to put this script on the same GameObject that contains our UI Document component.
var root = uiDocument.rootVisualElement;
var root = uiDocument.rootVisualElement;
私人按钮 charmButton;
private Button charmButton;
charmButton = root.Q<Button>("CharmButton");charmButton = root.Q<Button>("CharmButton");公共类 InspriationalPanel:MonoBehaviour
{
私人UIDocument uiDocument;
私人按钮 charmButton;
无效开始()
{
uiDocument = 获取组件<UIDocument>();
var root = uiDocument.rootVisualElement;
charmButton = root.Q<Button>("CharmButton");
charmButton.clicked += OnCharmClicked;
}
私有 void OnCharmClicked()
{
// 处理超级按钮点击
}
私有无效OnDisable()
{
charmButton.clicked-=OnCharmClicked;
}
}我想添加前面的大块代码,因为它基本上可以作为如何使用UI 工具包编写按钮代码的模板。
public class InspriationalPanel : MonoBehaviour
{
private UIDocument uiDocument;
private Button charmButton;
void Start()
{
uiDocument = GetComponent<UIDocument>();
var root = uiDocument.rootVisualElement;
charmButton = root.Q<Button>("CharmButton");
charmButton.clicked += OnCharmClicked;
}
private void OnCharmClicked()
{
// Handle charm button click
}
private void OnDisable()
{
charmButton.clicked -= OnCharmClicked;
}
}I wanted to add the preceding large block of code because it can essentially act as a template for how you will code buttons with the UI Toolkit.
VisualElement catPic = root.Q<VisualElement>("CatPic");VisualElement catPic = root.Q<VisualElement>("CatPic");私人IStyle catPicStyle;
private IStyle catPicStyle;
catPicStyle = catPic.样式;
catPicStyle = catPic.style;
IEnumerator 获取猫图片()
{
// 在这里实现协程逻辑
}IEnumerator GetCatPic()
{
// Implement coroutine logic here
}笔记
Note
在您输入 Web 请求之前,此方法将在您的 IDE 中显示错误。
This method is going to show an error in your IDE until you put in the web request.
确保将以下命名空间添加到您的代码中,以便它能够识别IEnumerator是什么:
Make sure to add the following namespace to your code so it recognizes what an IEnumerator is:
使用System.Collections;
using System.Collections;
int 随机宽度 = 随机范围(150, 300); int 随机高度 = 随机范围(150, 300); catPicStyle.宽度 = 随机宽度; catPicStyle.高度 = 随机高度;
int randomWidth = Random.Range(150, 300); int randomHeight = Random.Range(150, 300); catPicStyle.width = randomWidth; catPicStyle.height = randomHeight;
字符串 uri = “https://placekitten.com/” + randomWidth + “/” + randomHeight;
string uri = "https://placekitten.com/" + randomWidth + "/" + randomHeight;
UnityWebRequest 请求 = UnityWebRequestTexture.GetTexture(uri); 产生返回请求.SendWebRequest(); 如果(请求st.result != UnityWebRequest.Result.Success){ 调试.日志(请求.错误); } 别的 { // 使用返回的数据进行处理 }
上述代码尝试从网站获取纹理。如果请求失败, if会打印错误。确保导入以下命名空间,以便您的脚本了解UnityWebRequest是什么:
使用 UnityEngine.Networking;
UnityWebRequest request = UnityWebRequestTexture.GetTexture(uri); yield return request.SendWebRequest(); if (request.result != UnityWebRequest.Result.Success){ Debug.Log(request.error); } else { // Do stuff here with returned data }
The preceding code tries to get a texture from a website. The if prints an error if the request fails. Make sure to import the following namespace so your script understands what a UnityWebRequest is:
using UnityEngine.Networking;
Texture2D myTexture = ((DownloadHandlerTexture)request.downloadHandler).texture;
Debug.Log("已获取纹理");
catPicStyle.backgroundImage = 新的StyleBackground(myTexture);此代码将获取 Web 请求返回的图片并将其存储为 Texture2D 。然后将catPicStyle的背景图像设置为该Texture2D图像。
Texture2D myTexture = ((DownloadHandlerTexture)request.downloadHandler).texture;
Debug.Log("Texture Acquired");
catPicStyle.backgroundImage = new StyleBackground(myTexture);This code will take the picture returned by the web request and store it as a Texture2D. It then sets the background image of catPicStyle to that Texture2D image.
私有 void OnCharmClicked()
{
启动协同程序(GetCatPic());
}private void OnCharmClicked()
{
StartCoroutine(GetCatPic());
}If you play the game, you can now click the Charm Me button to get random cat pictures.
图 18.73:我们的 UI 中随机出现猫的图片
Figure 18.73: Random cat pictures appearing in our UI
为了避免这种情况发生,我们在加载新图像时删除原始图像。将以下代码添加为GetCatPic()协程的第一行:
catPicStyle.backgroundImage = null;
Instead of letting this happen, let’s remove the original image while the new image loads in. Add the following code as the first line of your GetCatPic() coroutine:
catPicStyle.backgroundImage = null;
私人按钮激发按钮; 自有品牌 inspirationalQuote;
private Button inspireButton; private Label inspirationalQuote;
私有 void OnInspireClicked()
{
// 实现单击 InspireButton 时的功能
}private void OnInspireClicked()
{
// Implement functionality for when the inspireButton is clicked
}inspireButton = root.Q<Button>("InspireButton");
inspireButton.clicked += OnInspireClicked;
inspirationalQuote = root.Q<Label>("InspirationalQuote");inspireButton = root.Q<Button>("InspireButton");
inspireButton.clicked += OnInspireClicked;
inspirationalQuote = root.Q<Label>("InspirationalQuote");IEnumerator GetInspiringQuote() {
UnityWebRequest 请求 = UnityWebRequest.Get("https://zenquotes.io/api/random");
产生返回请求.SendWebRequest();
如果(请求.结果!= UnityWebRequest.结果.成功){
调试.日志(请求.错误);
} 别的 {
Debug.Log("已获取报价");
字符串响应=请求.downloadHandler.文本;
调试.日志(响应.ToString());
// 更多代码将放在这里
}
}IEnumerator GetInspiringQuote() {
UnityWebRequest request = UnityWebRequest.Get("https://zenquotes.io/api/random");
yield return request.SendWebRequest();
if (request.result != UnityWebRequest.Result.Success) {
Debug.Log(request.error);
} else {
Debug.Log("Quote Acquired");
string response = request.downloadHandler.text;
Debug.Log(response.ToString());
// more code will go here
}
}You’ll notice this is structured similarly to the web request we used to get our cat image. Before we proceed, let’s explore how the data is returned.
私有 void OnInspireClicked()
{
启动协同程序(GetInspiringQuote());
}private void OnInspireClicked()
{
StartCoroutine(GetInspiringQuote());
}图 18.74: API 请求响应
如您所见,我们无法将完整结果放入标签的文本字段中。我们需要将其格式化为可用的格式,并仅获取我们想要的信息。为此,大多数简单来说,我们需要另一个包。
使用窗口|包管理器打开包管理器。
Figure 18.74: The API request response
As you can see, we can’t exactly put the full result into our Label’s text field. We need to format this into something usable and get only the information we want. To do this most simply, we’ll need another package.
Open the Package Manager with Window | Package Manager.
使用 Newtonsoft.Json.Linq;
如果您的 IDE 无法识别Newtonsoft ,请关闭 IDE 和 Unity,然后重新打开。
using Newtonsoft.Json.Linq;
If Newtonsoft is not recognized by your IDE, close the IDE and Unity and reopen it.
JArray jArray = JArray.Parse(响应);
JArray jArray = JArray.Parse(response);
JObject jObject = JObject.Parse(jArray[0].ToString());
JObject jObject = JObject.Parse(jArray[0].ToString());
字符串引用 = (字符串)jObject["q"]; 字符串作者 = (字符串)jObject["a"];
string quote = (string)jObject["q"]; string author = (string)jObject["a"];
inspirationalQuote.text = "\"" + quote + "\" \n~" + author;
玩游戏,您现在应该能够获得小猫的图片和励志名言!
inspirationalQuote.text = "\"" + quote + "\" \n~" + author;
Play the game and you should now be able to get pictures of kittens and inspirational quotes!
图 18.75:我们示例的最终版本
Figure 18.75: The final version of our example
由于 zenquotes 是一个 API,开发人员将调用次数限制为每 30 秒 5 次。这意味着如果您在 30 秒内尝试点击按钮超过 5 次,您将收到错误消息。
Because zenquotes is an API, the developers have put a limit of 5 calls per 30 seconds. So, that means if you try clicking the button more than 5 times in 30 seconds, you will get an error message.
你可以扩展这一点例如,在开始时获取一组数据并将其存储在本地。这可以减少一些图像的加载时间并减少所需的 API 调用次数。
You could expand on this example by getting a set of the data at the start and storing it locally. This could reduce some of the load times of the images and reduce the number of API calls needed.
这就是我将介绍的 UI Toolkit 的所有示例。正如我之前所说,这是一个大话题,也是开发 UI 的全新思路。我甚至无法介绍我想要介绍的内容的一半。如果您喜欢使用 UI Toolkit,我建议您查看资源部分以获取建议的进一步阅读和教程。
That’s all for the examples that I will cover for the UI Toolkit. As I said previously, this is a big topic and a whole new way of thinking about developing UI. I wasn’t able to cover even half of what I would have liked to. If you enjoy working with the UI Toolkit, I recommend viewing the resources section for suggested further reading and tutorials.
如果您正在寻找更多文档,我推荐Unity 提供的以下资源:
If you’re looking for more documentation, I recommend the following resources provided by Unity:
如果您对教程感兴趣,这里有一些很棒的资源。需要注意的是,目前大多数教程都是关于使用 UI Toolkit for Editor UI 的,因为运行时支持仍然很新:
If you’re interested in tutorials, here are some great resources. It’s important to note that most of the tutorials at this point are for using the UI Toolkit for Editor UI since the runtime support is still new:
最后,Unity 提供了一些使用 UI Toolkit 的优秀预制项目。您可以在资产商店中找到这些项目:
Lastly, Unity has provided some excellent pre-made projects that use the UI Toolkit. You can find these projects on the asset store here:
UI Toolkit 仍在开发中,但 Unity 正在积极将其开发为一个系统,以取代当前的 uGUI 系统(本书的其余部分到目前为止一直关注该系统)。它使用 Web 开发的概念来开发 UI,并且可以提供比 uGUI 更简洁、性能更高的 UI。但是,由于它仍处于开发阶段,因此它还不能完成 uGUI 所做的所有事情……目前还不能。虽然您可能无法完全过渡到 UI Toolkit 系统来满足您的 UI 需求,但了解它的工作原理会很有帮助,因为有一天,它将是您唯一的选择。
The UI Toolkit is still in development, but Unity is actively working on it as a system to replace the current uGUI system (that the rest of this book has focused on up to this point). It uses the concepts of web development to develop UI and can provide a cleaner, more performant UI than uGUI. However, since it is still in development, it doesn’t do everything uGUI does … yet. While you might not be able to fully transition over to the UI Toolkit system for your UI needs, it is helpful to have an idea of how it works, since one day, it will be your only option.
为了帮助您介绍 UI 工具包的概念,我们讨论了系统的一般概念以及如何使用它在编辑器和运行时 UI 中制作 UI。
To help introduce the concepts of the UI Toolkit to you, we discussed the general concepts of the system as well as how to use it to make UI in the Editor and Runtime UI.
在下一章中,我们将讨论Unity 中使用的另一个 UI 系统:IMGUI。
In the next chapter, we will discuss yet another UI system used within Unity: IMGUI.
我将介绍的最后一个 UI 系统是IMGUI,即即时模式图形用户界面。IMGUI 的主要用途是创建在开发和调试过程中协助开发人员的工具。虽然 IMGUI 在技术上可以制作运行时 UI,但 Unity 强烈反对这样做。因此,例如,您可以使用它来制作编辑器扩展或调试菜单,这些菜单将在您的游戏视图中运行,并且可以在编辑器外玩游戏时访问。但是,请记住,这些调试游戏内菜单是面向开发人员的,而不是面向玩家的。IMGUI 最常用于开发编辑器UI 扩展。
The last UI system I will cover is IMGUI or Immediate Mode Graphical User Interface. The primary usage for IMGUI is to create tools that assist developers during development and debugging. While IMGUI can technically make runtime UI, it is strongly discouraged by Unity. So, for example, you can use it to make Editor extensions or debug menus that will run in your game’s view, and can be accessed when you play your game outside of the Editor. However, keep in mind these debug in-game menus are meant to be developer-facing, not player-facing. IMGUI is most commonly used for developing Editor UI extensions.
由于本书主要关注运行时、面向玩家的 UI,因此我不会深入探讨这个系统;但是,我将介绍使用 IMGUI 制作面向开发人员的基础知识。
Since this book’s primary focus is on runtime, player-facing UI, I won’t delve too deep into this system; however, I’ll cover the very basics of making developer-facing with IMGUI.
在本章中,我将讨论以下内容:
In this chapter, I will discuss the following:
值得注意的是,IMGUI 不是 Unity 推荐的系统。当谈到运行时 UI 时,他们推荐 uGUI(本书的大部分内容都是关于这个的),而当谈到编辑器 UI 时,他们推荐 UI Toolkit(第 18 章中介绍过)。因此,学习 IMGUI 并不是 Unity 开发人员的必备技能。对于非程序员来说,它尤其不是一项必备技能。然而,它确实为程序员提供了一种非常快速的方法来构建 UI 以协助他们进行开发,因此这些技能并非毫无用处。
It is important to note that IMGUI is not a recommended system by Unity. When it comes to runtime UI, they recommend uGUI (which is what the majority of this book is about), and when it comes to Editor UI, they recommend UI Toolkit (which was covered in Chapter 18). So, learning IMGUI is not necessarily a required skill for anyone developing in Unity. It is especially not a required skill for non-programmers. However, it does give programmers a very quick way to build out UI to assist them in development, so the skills are not useless.
让我们回顾一下有关IMGUI的一些基本信息。
Let’s review some basic information about IMGUI.
您可以在此处找到本章的相关代码和资产文件:https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2019
You can find the relevant codes and asset files of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2019
正如我之前所说,IMGUI 提供程序员可以快速构建 UI 来协助他们进行开发。这是因为 IMGUI 完全通过代码构建。它不连接到 GameObjects,所有对象都通过调用OnGUI()或OnInspectorGUI()方法进行渲染。OnGUI ()方法每帧都会被调用,类似于Update()方法。
As I stated earlier, IMGUI gives programmers a quick way to build out UI that can assist them in their development. This is because IMGUI is built exclusively via code. It is not connected to GameObjects, and all objects are rendered via calls to an OnGUI() or OnInspectorGUI() method. The OnGUI() method is called every frame, similar to the Update() method.
如果您希望 IMGUI 出现在场景中,请在MonoBehaviour继承脚本中的OnGUI()方法中编写所有 UI 构建代码。由于 IMGUI 项目是通过代码创建的,因此您在MonoBehaviour脚本上使用它创建的任何 UI 都将在游戏运行之前呈现。
If you want your IMGUI to appear within your scene, you write all your UI building code in an OnGUI() method within a MonoBehaviour inheriting script. Because IMGUI items are created via code, any UI you create with it on a MonoBehaviour script will not render until the game is run.
如果您希望 UI 显示在编辑器窗口中,则应将所有 UI 构建代码写入EditorWindow继承脚本中的OnGUI()方法中。如果您希望 UI 显示在检查器中,则应将所有 UI 构建代码写入Editor继承脚本中的OnInspectorGUI()方法中。
If you want your UI to appear in an Editor window, you will write all your UI building code in an OnGUI() method within an EditorWindow inheriting script. If you want your UI to appear in the Inspector, you write all your UI building code in an OnInspectorGUI() method within an Editor inheriting script.
所有 IMGUI 项目都是通过在OnGUI()或OnInspectorGUI()方法中调用其独特方法创建的。每个独特方法都可以使用矩形位置进行定位和调整大小。使用矩形位置定位对象时,首先使用Rect()方法创建一个新的矩形。Rect ()方法采用四个参数:x位置、y位置、宽度和高度。因此,例如,您可以使用以下代码行创建一个新的矩形:
All IMGUI items are created by calling their unique method within an OnGUI() or OnInspectorGUI() method. Each of their unique methods can be positioned and sized using rectangular position. When positioning an object with rectangular position, you first create a new Rectangle using the Rect() method. The Rect() method takes four parameters: x position, y position, width, and height. So, for example, you can create a new rectangle with the following line of code:
矩形 rect = 新矩形 (10, 10, 50, 50);
Rect rect = new Rect(10, 10, 50, 50);
这将在屏幕坐标(10, 10)处创建一个宽度和高度为50的矩形。
This would create a rectangle at screen coordinate (10, 10) with a width and height of 50.
请记住,屏幕坐标将位置(0,0)置于屏幕的左上角。
Remember, screen coordinates put position (0,0) in the top-left corner of the screen.
您还可以使用Vector2来指定参数。例如,您可以执行如下操作来实现相同的结果:
You can also use Vector2s to specify the parameters. For example, you could do something like this to achieve the same results:
Vector2 位置 = 新 Vector2(10, 10); Vector2 维度 = new Vector2(50, 50); Rect rect = new Rect(位置,尺寸);
Vector2 position = new Vector2(10, 10); Vector2 dimensions = new Vector2(50, 50); Rect rect = new Rect(position, dimensions);
现在我们知道如何创建 IMGUI 项目,让我们回顾一下可以使用OnGUI()绘制的项目类型。
Now that we know how to create IMGUI items, let’s review the types of items that you can draw using OnGUI().
绘制的项目使用 IMGUI 的控件称为控件。虽然“控件”一词可能暗示该项目是可交互的,但并非所有控件是可交互的。IMGUI 系统提供多种控件,但您可以使用以下控件实现大多数 IMGUI 目标:
The items drawn using IMGUI are called Controls. While the term “Controls” may imply the item is interactable, not all Controls are interactable. There are multiple Controls available with the IMGUI system, but you can accomplish most of your IMGUI goals with the following:
笔记
Note
您可以在此处找到所有 IMGUI 控件的完整列表:https ://docs.unity3d.com/2023.3/Documentation/Manual/gui-Controls.xhtml 。
You can find a comprehensive list of all IMGUI controls here: https://docs.unity3d.com/2023.3/Documentation/Manual/gui-Controls.xhtml.
Label是非交互式项目。它可以是文本或图像。要创建Label,请调用GUI.Label()方法。GUI.Label ()方法有多个重载,但主要重载是您将使用以下工具:
A Label is a non-interactive item. It can be either text or an image. To create a Label, you call the GUI.Label()method. The GUI.Label() method has multiple overloads, but the primary ones you will use are the following:
公共静态void标签(矩形位置,字符串文本); 公共静态无效标签(矩形位置,纹理图像);
public static void Label(Rect position, string text); public static void Label(Rect position, Texture image);
其中第一个用于定义文本标签,第二个用于定义图像标签。
Where the first is used to define a text label, and the second is used to define an image label.
例如,以下代码当它附加到 GameObject 并且在Inspector中分配labelTexture变量时,将在场景中显示文本标签和图像标签:
For example, the following code will display a text label and an image label in a scene, when it is attached to a GameObject and the labelTexture variable is assigned in the Inspector:
公共类第 19 章标签:MonoBehaviour {
公共Texture2D标签纹理;
私有无效OnGUI(){
GUI.Label(new Rect(10, 10, 100, 50), "文本标签");
GUI.Label(new Rect(10, 80, 50, 50), labelTexture);
}
}public class Chapter19Labels : MonoBehaviour {
public Texture2D labelTexture;
private void OnGUI() {
GUI.Label(new Rect(10, 10, 100, 50), "Text Label");
GUI.Label(new Rect(10, 80, 50, 50), labelTexture);
}
} 标签看起来如下:
The labels will look as follows:
图 19.1:使用 IMGUI 标签
Figure 19.1: Using IMGUI Labels
按钮控件只需单击即可执行一项功能。单击并按住按钮时,它只会触发一次。它可以可以是文本按钮或图像按钮。要创建按钮,请调用GUI.Button()方法。GUI.Button ()方法有多个重载,但您将使用的主要重载如下:
A Button Control performs a function with a single click. It only triggers once if the button is clicked and held. It can be a text button or an image button. To create a Button, you call the GUI.Button() method. The GUI.Button() method has multiple overloads, but the primary ones you will use are the following:
公共静态 bool 按钮(矩形位置,字符串文本); 公共静态bool按钮(矩形位置,纹理图像);
public static bool Button(Rect position, string text); public static bool Button(Rect position, Texture image);
第一个将用于定义一个文本按钮,第二个将用于定义一个图像按钮。
Where the first will be used to define a text button, and the second will be used to define an image button.
要在单击按钮时执行代码,请在OnGUI()方法内的if语句中创建按钮。
To execute code when a button is clicked, you create the button within an if statement within the OnGUI() method.
例如,以下代码将在场景中显示一个文本按钮和一个图像按钮:
For example, the following code will display a text button and an image button in a scene:
公共类第 19 章按钮:MonoBehaviour {
公共 Texture2D 按钮纹理;
私有无效OnGUI(){
如果(GUI.Button(new Rect(10, 10, 100, 50),“文本按钮”)){
Debug.Log("文本按钮被点击");
}
如果(GUI.Button(新Rect(10,80,50,50),buttonTexture)){
Debug.Log("图像按钮被点击");
}
}
}public class Chapter19Buttons : MonoBehaviour {
public Texture2D buttonTexture;
private void OnGUI() {
if (GUI.Button(new Rect(10, 10, 100, 50), "Text Button")) {
Debug.Log("Text Button Clicked");
}
if (GUI.Button(new Rect(10, 80, 50, 50), buttonTexture)) {
Debug.Log("Image Button Clicked");
}
}
} 两个按钮都会写入每次单击时,控制台中都会显示一条消息。代码必须附加到 GameObject,并且必须在Inspector中分配buttonTexture变量。
Both buttons will write a message in the console each time they are clicked. The code must be attached to a GameObject and the buttonTexture variable must be assigned in the Inspector.
上述代码创建的按钮如下所示:
The buttons created by the preceding code will look as follows:
图 19.2:使用 IMGUI 按钮
Figure 19.2: Using IMGUI Buttons
如果你想创建一个按钮,在点击并按住时调用一个方法,你可以使用RepeatButton。它的创建代码与创建Button几乎相同。你可以将上述代码中的所有GUI.Button实例替换为GUI.RepeatButton,并实现结果类似,不同之处在于只要按住按钮,按钮就会执行该方法。
If you want to create a button that calls a method as it is clicked and held, you can use a RepeatButton. Its creation code is nearly identical to the creation of a Button. You can replace all instances of GUI.Button in the preceding code with GUI.RepeatButton and achieve a similar result, except the button will execute the method as long as the button is held.
TextField和TextArea控件允许您创建一个可编辑的交互式框文本。当您只需要一行可编辑文本时,可以使用TextField ;而当您需要多行文本时,可以使用TextArea 。要创建TextField或TextArea,请分别调用GUI.TextField()和GUI.TextArea()方法。GUI.TextField ()方法有多个重载,但您将使用的主要方法如下:
The TextField and TextArea Controls allow you to create an interactive box with editable text. TextField is used when you want only a single line of editable text, whereas TextArea is used when you want multiple lines. To create a TextField or TextArea, you call the GUI.TextField() and GUI.TextArea() methods, respectively. The GUI.TextField() method has multiple overloads, but the primary ones you will use are the following:
公共静态字符串 TextField (矩形位置,字符串文本); 公共静态字符串 TextArea(矩形位置,字符串文本);
public static string TextField(Rect position, string text); public static string TextArea(Rect position, string text);
第一个方法创建一个TextField,第二个方法创建一个TextArea。在这两种情况下,字符串参数都是在用户开始编辑文本之前显示的文本。请注意,该方法返回的是字符串类型。您可以通过将构造的对象分配给字符串变量来获取用户输入的值。
The first method creates a TextField, and the second creates a TextArea. In both cases, the string parameter is the text that will be displayed before the user begins editing the text. Note that the method returns a string type. You can get the value of what is entered by the user by assigning the constructed object to a string variable.
例如,以下代码将显示一个TextField和一个 TextArea,当它附加到GameObject 时,您可以在场景中与之交互:
For example, the following code will display a TextField and TextArea you can interact with in your scene when it is attached to a GameObject:
公共类第 10 章 TextFieldAndArea : MonoBehaviour {
private string textFieldText = "输入文本";
private string textAreaText = "输入文本";
私有无效OnGUI(){
文本字段文本 = GUI.文本字段(新Rect(10, 10, 100, 50), 文本字段文本);
文本区域文本 = GUI.文本区域(新 Rect(10, 80, 100, 100), 文本区域文本);
}
}public class Chapter10TextFieldAndArea : MonoBehaviour {
private string textFieldText = "Enter text";
private string textAreaText = "Enter text";
private void OnGUI() {
textFieldText = GUI.TextField(new Rect(10, 10, 100, 50), textFieldText);
textAreaText = GUI.TextArea(new Rect(10, 80, 100, 100), textAreaText);
}
} The items rendered in the scene appear as follows before the user starts editing the text:
图 19.3:使用 IMGUI TextField 和 TextArea 控件
Figure 19.3: Using the IMGUI TextField and TextArea Controls
Toggle控件是一个复选框,单击即可改变其状态。要创建Toggle,请调用GUI.Toggle()方法。GUI.Toggle ()方法有多个重载,但您主要需要的是将使用以下内容:
A Toggle Control is a checkbox that changes its state with a click. To create a Toggle, you call the GUI.Toggle() method. The GUI.Toggle() method has multiple overloads, but the primary ones you will use are the following:
公共静态 bool Toggle(Rect 位置,bool 值,字符串文本);
public static bool Toggle(Rect position, bool value, string text);
注意该方法返回的是bool类型,你可以通过将创建的对象赋值给bool变量来获取切换按钮的值。
Note that the method returns a bool type. You can get the value of the toggle by assigning the created object to a bool variable.
例如,以下代码将创建一个切换按钮,每当单击该切换按钮时,其值将分配给布尔变量。与其他示例一样,此示例需要附加到场景中的 GameObject才能渲染:
For example, the following code will create a toggle whose value is assigned to a Boolean variable whenever the toggle is clicked. This example, like the other examples, would need to be attached to a GameObject in your scene to render:
公共类第 19 章切换:MonoBehaviour {
私有 bool toggleBool = true;
私有无效OnGUI(){
toggleBool = GUI.Toggle(new Rect(10, 10, 100, 50), toggleBool,"切换我");
}
}public class Chapter19Toggle : MonoBehaviour {
private bool toggleBool = true;
private void OnGUI() {
toggleBool = GUI.Toggle(new Rect(10, 10, 100, 50), toggleBool,"Toggle Me");
}
} 切换按钮最初将在场景中按如下方式呈现,直到用户与其交互,在这种情况下,切换按钮将在每次点击时打开和关闭。
The toggle will initially render in the scene as follows until the user interacts with it, in which case the toggle will turn on and off with each click.
图 19.4:使用 IMGUI 切换控件
Figure 19.4: Using the IMGUI Toggle Control
我展示的所有示例都在场景中。但是,如果您想在编辑器窗口中显示 UI,您的代码将以类似的方式工作。您只需将代码放在EditorWindow继承脚本中即可。(请参阅第 18 章中介绍的示例。)但是,如果您想在 Inspector 中创建 IMGUI,情况会略有不同。现在让我们看看。
All the examples I have shown were in the scene. However, if you want to display your UI in an Editor window, your code will work similarly. You simply put your code in an EditorWindow inheriting script. (See the example covered in Chapter 18.) However, it is slightly different if you want to create IMGUI in your Inspector. Let’s look at that now.
使用 IMGUI 增强组件的方式与使用游戏内 IMGUI 和EditorWindow IMGUI的方式非常相似。但是,有一些小的变化。首先,您要编写从Editor继承的脚本。其次,您使用OnInspectorGUI()方法,而不是OnGUI()方法。第三,如果您希望 Inspector 还包含其所有常用数据,则需要在OnInspectorGUI()方法中调用DrawDefaultInspector()方法。最后,如果您希望 IMGUI 按钮与各种默认 Inspector 元素一致,您使用GUILayout基类,而不是GUI基类。因此,例如,您不会使用以下代码创建按钮:
Using IMGUI to enhance your components works very similarly to the way it does with in-game IMGUI and EditorWindow IMGUI. However, there are some small changes. First, you write scripts that inherit from Editor. Second, you use the OnInspectorGUI() method, not the OnGUI() method. Third, if you want the Inspector to also contain all its usual data, you need to call the DrawDefaultInspector() method within your OnInspectorGUI() method. Lastly, if you want the IMGUI button to appear in line with the various default Inspector elements, you use the GUILayout base class, rather than the GUI base class. So, for example, you wouldn’t create a button with the following code:
GUI.Button(new Rect(10, 10, 100, 50), "文本按钮");
GUI.Button(new Rect(10, 10, 100, 50), "Text Button");
相反,你可以用这个来创建它:
Instead, you’d create it with this:
GUILayout.Button("文本按钮");GUILayout.Button("Text Button"); 使用GUILayout创建的按钮不需要矩形位置,并且会自动定位在UI 内。
Buttons created with GUILayout do not require a rectangular position and will automatically be positioned within the UI.
我将在示例部分介绍一个这样的例子。
I will cover an example of this in the Examples section.
现在,您已经掌握了足够的基本背景信息,可以开始使用 IMGUI 开发面向开发人员的 UI。让我们看一些基本示例,帮助您开始使用该系统。
Now, you have enough basic background information to begin developing developer-facing UI with IMGUI. Let’s look at some basic examples to get you started on using the system.
对于本章中的示例,我将介绍两种类型的 IMGUI 用法:一种用于游戏内调试菜单,另一种用于检查器 UI。到目前为止介绍的所有示例都是游戏内调试菜单 UI。
For the examples in this chapter, I will cover two types of IMGUI usages: one for an in-game debug menu and another for an Inspector UI. All the examples covered up to this point have all been in-game debug menu UI.
让我们创建一个非常简单的脚本,它将在场景中显示游戏的帧速率。如果帧速率低于某个值,它将改变颜色。
Let’s create a very simple script that will show the frame rate of our game in the scene. It will change color if the framerate drops below a certain value.
To display the framerate in your game, complete the following steps:
int fps; [序列化字段] int fpsThreshold;
fps变量用于估计游戏的帧速率,而fpsThreshold变量在 Inspector 中分配并用于确定 fps 在场景中显示为红色的阈值。
int fps; [SerializeField] int fpsThreshold;
The fps variable is used to get an estimate of our game’s frame rate, while the fpsThreshold variable is assigned in the Inspector and used to determine the threshold for when our fps will display as red in the scene.
私有无效OnGUI(){
GUI.Label(新Rect(10,10,50,50),“fps =”+fps.ToString());
}private void OnGUI() {
GUI.Label(new Rect(10, 10, 50, 50), "fps = " + fps.ToString());
}私有无效更新(){
fps = (int)(1f / Time.unscaledDeltaTime);
}private void Update() {
fps = (int)(1f / Time.unscaledDeltaTime);
}图 19.5:游戏中显示的帧速率
如果不暂停游戏,就很难看到该值是多少。所以,当它降到某个值以下时,让我们让它改变颜色。这将使我们更容易看到我们担心的帧速率。我们将为此使用 fpsThreshold 值。您在编辑器中运行的游戏可能以与我的不同的帧速率运行,因此请使用对您的系统有意义的值,以便能够看到代码的执行。我将使用1000 。在Inspector中输入您的fpsThreshould的值。
Figure 19.5: The frame rate displaying in-game
It’s slightly difficult to see what the value is without pausing the game. So, let’s make it change color when it drops below a certain value. This will make it easier to see when there is a framerate we find worrisome. We’ll use the fpsThreshold value for this. Your game running in your Editor may run at a different framerate than mine, so please use a value that makes sense for your system to be able to see the code execute. I’m going to use 1000. Enter the value for your fpsThreshould in the Inspector.
如果 (fps < fpsThreshold) {
GUI.内容颜色 = 颜色.红色;
}
别的 {
GUI.内容颜色 = 颜色.白色;
}if (fps < fpsThreshold) {
GUI.contentColor = Color.red;
}
else {
GUI.contentColor = Color.white;
}这就是您在游戏中需要显示帧速率估计值的全部内容。
And that’s all you need to have a framerate estimate display in your game.
显示游戏的帧速率是你可能想要通过游戏内调试实现的一个很好的例子UI。这样,即使您在编辑器之外运行游戏,也能轻松查看游戏的总体性能。您可以将其扩展为仅在执行特定任务时显示,或仅每秒更新一次。可能性无穷无尽。
Displaying a framerate for your game is a great example of something you might want to make with an in-game debug UI. This will let you easily see the general performance of your game, even when you run it outside of the Editor. You could extend this to only appear when you perform specific tasks or only update every second. The possibilities are endless.
您可能还想使用游戏内调试菜单,原因有很多。例如,您可能需要按钮来调用帮助您跳转到游戏各个部分的函数。或者,您可能需要一个按钮来清除已保存的数据。使用 IMGUI 制作游戏内 UI 时要记住的重要一点是,这一切都应该是为了帮助您(开发人员),而不应该用于向玩家显示信息。
There are lots of other reasons you may want to use an in-game debug menu. For example, you might want buttons that call functions that help you skip to sections of your game. Or maybe you want a button that clears your saved data. The important thing to remember about making in-game UI with IMGUI is this should all be to help you, the developer, but should not be used to display information to your player.
现在,让我们看一个您可以在编辑器中执行的操作来协助您的开发的示例。
Now, let’s look at an example of something you can do in your Editor to assist your development.
在游戏开发中,你通常会将数据存储在某些外部源中,并需要将其导入为可用的格式您的游戏代码。例如,您的团队中可能有一个作家,他在 Excel 表中创建所有对话,然后您需要弄清楚如何将其导入到您的游戏中。此示例将展示一个基本示例,该示例使用 Inspector 按钮从文件中读取文本并将其分发到游戏中的适当位置,特别是ScriptableObject。
Often in game development, you will have data stored in some external source and need to import it into a usable format within your game’s code. For example, you may have a writer on your team who creates all dialogue in an Excel sheet, which you then need to figure out how to import into your game. This example will show a basic example that uses an Inspector button to read text from a file and distribute it to the appropriate place within your game, specifically a ScriptableObject.
图 19.6:带有导入按钮的自定义检查器
Figure 19.6: A custom Inspector with an import button
笔记
Note
我们在本文中还没有真正讨论过 ScriptableObject。ScriptableObject 本质上是Unity 中的数据容器。
We haven’t really discussed ScriptableObjects in this text. A ScriptableObject is essentially a data container within Unity.
创建导入按钮数据放入你的ScriptableObject中,完成以下步骤:
To create a button that imports data into your ScriptableObject, complete the following steps:
公共类 DialogueData : ScriptableObject {public class DialogueData : ScriptableObject {公共字符串文本路径; 公共列表<string>导入的对话;
我们将使用textPath变量来定义在项目中导入的文本文件的位置,并使用importedDialogue变量来保存所有导入的对话。
public string textPath; public List<string> importedDialogue;
We’ll use the textPath variable to define where the text file being imported is within our project and the importedDialogue variable to hold all the imported dialogue.
[CreateAssetMenu(fileName =“新 SO”,menuName =“DialogueData”,order = 1)]
[CreateAssetMenu(fileName = "New SO", menuName = "DialogueData", order = 1)]
图 19.7:创建 DialogueData ScriptableObject
这将创建一个名为New SO的新 ScriptableObject,并具有以下 Inspector:
图 19.8:ScriptableObject 的检查器
Figure 19.7: Creating the DialogueData ScriptableObject
This will create a new ScriptableObject called New SO with the following Inspector:
Figure 19.8: The ScriptableObject’s Inspector
公共类DialogueDataCustomEditor:编辑器{public class DialogueDataCustomEditor : Editor {使用 UnityEditor;
using UnityEditor;
[自定义编辑器(类型(DialogueData))]
[CustomEditor(typeof(DialogueData))]
公共覆盖无效OnInspectorGUI()
{
if (GUI.Button(new Rect(10, 10, 100, 50), "导入按钮"))
{
// 在此处理按钮点击逻辑
}
}public override void OnInspectorGUI()
{
if (GUI.Button(new Rect(10, 10, 100, 50), "Import Button"))
{
// Handle button click logic here
}
}If you return to your New SO, you’ll see that the Inspector only contains a button now.
图 19.9:检查器中的按钮
Figure 19.9: The button in the Inspector
绘制默认检查器();
DrawDefaultInspector();
图 19.10:检查器中默认信息上的按钮
编辑按钮代码如下:
if (GUILayout.Button("导入对话框")) {
} 这将导致按钮显示在组件的底部,利用 Unity GUI 的布局,而不是明确定位。
图 19.11:使用 GUILayout 基类的按钮
Figure 19.10: The button in the Inspector over the default information
Edit the button code to the following:
if (GUILayout.Button("Import Dialogue")) {
} This will cause the button to display at the bottom of the component, utilizing the layout of the Unity GUI instead of being explicitly positioned.
Figure 19.11: The button using the GUILayout base class
私有字符串[] splitTags = {“\r\n”,“\r”,“\n”};我们将使用这个变量来正确解析文本文件中的数据,确保每一行都是一行新的对话。
private string[] splitTags = { "\r\n", "\r", "\n"};We will use this variable to correctly parse the data in the text file, making sure each new line is a new line of dialogue.
私有无效ReadString(){
对话数据对话数据脚本 = (对话数据)目标;
StreamReader reader = new StreamReader(dialogueDataScript.textPath);
ParseFile(reader.ReadToEnd());
读者.关闭();
}
私有 void ParseFile(字符串 theFileText){
调试.日志(文件文本);
对话数据对话数据脚本 = (对话数据)目标;
dialogDataScript.importedDialogue.Clear();
字符串[] 行 = theFileText.Split(splitTags,StringSplitOptions.None);
foreach (var line in lines) {
dialogueDataScript.importedDialogue.添加(行);
EditorUtility.SetDirty(目标);
}
}private void ReadString() {
DialogueData dialogueDataScript = (DialogueData)target;
StreamReader reader = new StreamReader(dialogueDataScript.textPath);
ParseFile(reader.ReadToEnd());
reader.Close();
}
private void ParseFile(string theFileText) {
Debug.Log(theFileText);
DialogueData dialogueDataScript = (DialogueData)target;
dialogueDataScript.importedDialogue.Clear();
string[] lines = theFileText.Split(splitTags, StringSplitOptions.None);
foreach (var line in lines) {
dialogueDataScript.importedDialogue.Add(line);
EditorUtility.SetDirty(target);
}
}公共覆盖无效OnInspectorGUI(){
绘制默认检查器();
if (GUILayout.Button("导入对话框")) {
读取字符串();
}
}public override void OnInspectorGUI() {
DrawDefaultInspector();
if (GUILayout.Button("Import Dialogue")) {
ReadString();
}
}图 19.12:在 Inspector 中分配的 textPath 变量
Figure 19.12: The textPath variable assigned in the Inspector
这就是使用 IMGUI 在编辑器中创建按钮的方法。
And that’s it for using IMGUI to create a button within the Editor.
在本章中,我们讨论了如何使用 IMGUI 系统为开发人员、游戏内调试显示以及编辑器扩展构建 UI。虽然 IMGUI 不一定是推荐的 UI 系统,但它对于创建超快速工具来协助开发人员完成开发过程非常有用。
In this chapter, we discussed how to use the IMGUI system to build UI for both developers, in-game debug displays as well as Editor extensions. While IMGUI is not necessarily a recommended UI system, it is extremely helpful for creating super-quick tools to assist developers during the development process.
在下一章中,我们将了解 Unity 提供的另一个输入系统:新输入系统。
In the next chapter, we will look at the other input system provided by Unity: the New Input System.
Unity 的输入系统(通俗地说称为新输入系统)允许您使有关游戏如何响应来自各种输入设备的交互的代码更加模块化。旧输入系统使用输入管理器来允许您定义可在代码中引用的各种轴(请参阅第 8 章)。您将在Update()方法中创建检查以确定设备是否收到输入。这通常会在您的Update()方法中产生多个 if-else 分支,用于控制收到的每个输入发生的情况。
Unity’s Input System (colloquially referred to as the new Input System) allows you to make the code concerning how your game reacts to interactions from various input devices more modular. The old Input System used the Input Manager to allow you to define various Axes that could be referenced in code (see Chapter 8). You would create checks in your Update() method to determine if a device received an input. This usually results in multiple if-else branches within your Update() method that control what happens for each input received.
输入系统使用基于事件的编程方法,将单个输入设备的处理与代码分离开来。您无需在代码中创建对每个输入设备的引用,而是创建对特定操作作出反应的代码。然后,输入系统控制哪个输入设备的哪个交互触发这些操作。
The Input System divorces the handling of individual input devices from code by using an event-based programming methodology. Instead of having to create reference to each of your input devices within your code, you create code that reacts to specific actions. The Input System then controls which Interaction from which Input devices trigger these actions.
这种输入处理和代码分离允许您制作更多可自定义的控件,更轻松地处理不同的输入设备,以及更轻松地处理来自具有截然不同控制方案的不同控制台的控件。此外,输入系统的模块化允许您轻松地将控制方案复制到其他项目,因为所有信息都存储在资产中。
This separation of input handling and code allows you to make more customizable controls, more easily handle different Input Devices, and more easily handle controls from different consoles that have extremely different control schemes. Additionally, the modularity of the Input System allows you to easily copy your control schemes to other projects, since the information is all stored on an asset.
本章旨在介绍输入系统并概述关键概念,以便您可以在项目中开始使用输入系统。
This chapter is meant to be an introduction to the Input System and will give you an overview of the key concepts so that you can start using the Input System within your projects.
在本章中,我将讨论以下内容:
In this chapter, I will discuss the following:
在我们开始了解如何使用输入系统之前,需要将其导入到您的项目中。让我们看看如何做到这一点。
Before we begin looking at how to work with the Input System, it needs to be imported into your project. Let’s look at how to do that.
您可以在此处找到本章的资产文件和代码: https: //github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2020
You can find the asset files and codes of this chapter here: https://github.com/PacktPublishing/Mastering-UI-Development-with-Unity-2nd-Edition/tree/main/Chapter%2020
输入系统是不再是预览版,而是正式成为 Unity 的一部分。但是,它不是预先打包在 Unity 中的,必须安装。您可以通过包管理器将输入系统安装到您的项目中,只需完成以下步骤即可。
The Input System is no longer in preview and is officially part of Unity. However, it does not come pre-packaged in unity and must be installed. You can install the Input System into your Project via the Package Manager, by completing the following steps.
图 20.1:在包管理器中更改包过滤器
Figure 20.1: Changing the Package filter in the Package Manager
图 20.2:在包管理器中查找输入系统
Figure 20.2: Finding the Input System in the Package Manger
您将看到以下弹出窗口,您必须同意才能继续。
You will see the following pop up which you must agree to in order to proceed.
图 20.3:关于更改输入系统的警告
Figure 20.3: A Warning about changing input systems
警告是表示您使用旧输入系统编写的任何代码将不再起作用。这是一种破坏性操作,可能会破坏您的游戏。
The warning is indicating that any code you wrote using the old Input System will no longer function. This is a destructive action and may break your game.
笔记
Note
我建议您创建一个新项目来探索输入系统,因为安装它可能对您的项目造成破坏。它将导致使用输入管理器编写的所有代码停止运行。除非您觉得这样做很方便,否则请不要将其安装在现有项目中。
I recommend you create a new project to explore the Input System as installing it may be destructive to your project. It will cause all code written using the Input Manager to cease functioning. Don’t install it in a pre-existing project until you are comfortable doing so.
重新打开项目后,您可能需要返回到包管理器并安装 Unity 随包提供的各种示例。我建议您安装简单演示和 UI vs Game Input。
Once you reopen your project, you may want to return to the Package Manager and install the various Samples provided by Unity with the Package. I recommend you install the Simple Demo and UI vs Game Input.
由于这是一个新的输入系统,因此它确实使用了一种比旧输入系统新的方法来访问输入。在开始研究之前,让我们先讨论一下这些方法之间的差异输入系统的各个元素。
Since this is a new Input System, it does use a new methodology to access inputs than the old Input System. Let’s first discuss the differences in these methodologies before we begin looking at the various elements of the Input System.
我们讨论过第 8 章中的输入管理器和直到最近,这是在 Unity 构建的游戏中跟踪设备输入的唯一方法。使用输入管理器时,您可以将特定的轴分配给不同的输入操作。然后,编写一个 C# 脚本,不断检查是否执行了该操作。
We discussed the Input Manager in Chapter 8 and up until recently, it was the only way to track inputs from devices in a Unity-built game. When using the Input Manager, you assign specific Axes to different input actions. You then write a C# script that constantly checks to see if that action is performed.
图 20.4:监视输入管理器定义的特定输入的 C# 脚本
Figure 20.4: C# script watching for specific inputs defined by the Input Manager
为了实现这一点,您的代码看起来应该类似于以下伪代码:
To accomplish this, your code would look something like the following pseudocode:
无效更新(){
如果(某些输入发生){
做某事
}
}void Update () {
if (some input happened){
do something
}
} 这种定期请求信息的技术称为轮询。您可以使用轮询模式从输入系统访问信息,方式与使用输入管理器的方式类似。但是,为了使您的代码更加模块化,使用输入管理器时,大多数代码系统将使用发布者-订阅者(pub-sub)模式。
This technique of requesting information at a regular interval, is called polling. You can use the polling pattern for accessing information from the Input System in a similar way that you could with the Input Manager. However, to make your code more modular, most of your code when using the Input System will use a publisher-subscriber (pub-sub) pattern.
考虑以下类比:你非常喜欢某个 YouTube 内容创作者的内容。你每天都会查看他们的频道,看看他们是否发布了新内容。但是,你觉得这很麻烦,所以决定订阅他们的频道。现在,YouTube 频道会在发布新内容时提醒你,为你省去了不少工作。想象一下,如果你有数百个 YouTube 频道,想通过订阅他们的频道来了解最新动态,那么你可以省去多少工作量反而的不断地检查他们的情况。
Consider the following analogy: you really enjoy content from a specific YouTube content creator. You check their channel every day to see if they have released new content. However, you find this cumbersome and instead decide to subscribe to their channel. Now the YouTube channel will alert you whenever new content is posted, saving you the work. Imagine how much work you save yourself from if you had hundreds of YouTube channels you wanted to be updated on by subscribing to their channels instead of constantly checking in on them.
让我们将这个类比与编码联系起来。您的 C# 脚本不必不断检查Update()方法中的各种输入,而是可以订阅由输入系统定义的特定事件。用更专业的术语来说,您将编写事件订阅者方法来监听由输入系统引发的特定事件。
Let’s tie the analogy to coding. Instead of your C# script having to constantly check in on the various inputs in the Update() method, you can instead subscribe to specific events defined by the Input System. In more technical speak, you will write event subscriber methods that listen for specific events raised by the Input System.
图 20.5:监听新输入系统定义的特定事件的 C# 脚本
Figure 20.5: C# script listening for specific events defined by the New Input System
因此,当使用新输入系统时,您的代码可能看起来像以下伪代码:
So, when using the New Input System, your code may look something like the following pseudocode:
无效OnEnable(){
订阅 DoSomething() 事件
}
私人做某事(){
做某事
}
无效OnDisable(){
取消订阅 DoSomething() 事件
}void OnEnable() {
subscribe DoSomething() to the event
}
private DoSomething() {
do something
}
void OnDisable() {
unsubscribe DoSomething() from the event
} 输入系统将根据您指定的输入确定调用哪些事件,并且您的代码将订阅这些事件,并在这些事件发生时执行适当的功能。
The Input System will determine which events to call based on inputs you specify and your code will subscribe to those events, performing the appropriate functions whenever those events happen.
那么,如何告诉输入系统根据哪种输入调用哪些事件?以及哪些类型的可以从这些输入调用事件吗?通过使用操作、交互和输入绑定来实现这一点。现在让我们来探讨一下这些概念。
So, how do you tell the Input System which events to call based on which input? And what types of events can it call from those inputs? You do this through the use of Actions, Interactions, and Input Binding. Let’s explore these concepts now.
在输入中在管理器中,您可以列出各种轴并定义哪些按钮可以触发这些轴。在输入系统中,您可以定义一组操作,并通过输入绑定描述输入设备的各种控件可以触发哪些交互这些操作。
In the Input Manager, you list various Axes and define what buttons can trigger these axes. In the Input System, you define a set of Actions and describe what Interactions the various Controls of the Input Device can trigger those Actions through Input Bindings.
例如,您可以创建输入绑定,通过按下交互将键盘上的空格键绑定到跳跃动作,其中输入设备是键盘,控件是键盘上的所有键。
For example, you could create the Input Binding that binds the Space Bar on a keyboard to a jump Action through a pressed Interaction, where the Input device would be the keyboard and the Controls would be all the keys on the keyboard.
图 20.6:输入绑定的示例
Figure 20.6: An example of an Input Binding
您可以创建这些行动集在已知的范围内作为动作图。动作图包含动作列表。动作包含有关输入绑定的信息。一般的想法是创建包含基于其目的的动作组的动作图,例如,所有角色控制动作都可以放在一个动作图中,而所有 UI 动作都可以放在单独的动作图中。
You can create these sets of Actions within what is known as an Action Map. Action Maps contain a list of Actions. The Actions contain the information about the Input Bindings. The general idea is to create Action Maps that contain groups of actions based on their purpose for example all character control Actions may go in one Action Map and all UI Actions may go in a separate one.
您可以在操作编辑器中查看操作地图、操作、绑定和交互。例如,以下是动作编辑器包含两个动作映射,分别称为播放器 (Player) 和UI (UI)。
You can view your Action Maps, Actions, Bindings, and Interactions in the Action Editor. For example, here is the Action Editor with two Action Maps called Player and UI.
图 20.7:动作编辑器
Figure 20.7: The Action Editor
在玩家行动中地图上,您可以看到跳跃动作,以及空格[键盘]绑定和按压交互。
Within the Player Action Map, you can see the Jump Action, with the Space [Keyboard] Binding, and the Press Interaction.
要开始创建操作和操作映射,请在Assets文件夹中单击鼠标右键,然后选择“创建|输入操作” ,然后将新的输入操作资产命名为您想要的任何名称。我建议您将这些资产保存在 Assets文件夹中名为“输入”的文件夹中。执行此操作后,您应该得到如下所示的内容:
To start creating your Actions and Action Maps, right click within an Assets folder and select Create | Input Actions and name the new Input Action Asset whatever you wish. I recommend you save these assets in a folder called Inputs within your Assets folder. When you do so, you should get something that looks like the following:
图 20.8:动作资产的检查器和图标表示
Figure 20.8: An Action Asset’s Inspector and icon representation
您可以双击刚刚创建的资产,也可以从其检查器中选择“编辑资产”来查看动作编辑器。动作编辑器将包含动作图和动作列表,同时显示每个动作的属性。
You can either double click on the asset you just created or select Edit asset from its Inspector to view the Action Editor. The Action Editor will contain your list of Action Maps and Actions while displaying each of the Action’s properties.
图 20.9:新创建的动作资产的动作编辑器
Figure 20.9: The Action Editor of a newly created Action Asset
选择“动作地图”部分中的+号将创建一个新的动作地图。
Selecting the + sign in the Action Maps section will create a new Action Map.
新的动作地图将自动附带一个新动作,您可以对其进行重命名。
A new Action Map will automatically come with a New action, which you can rename.
图 20.10:带有新动作的动作地图
Figure 20.10: An Action Map with its New action
选择<无绑定>将允许您创建输入绑定。
Selecting <No Binding> will allow you to create an Input Binding.
我将在本文末尾的示例部分介绍如何选择“绑定和交互”的示例章节,但是现在,让我们看看如何将这些操作挂接到您的代码上。
I will cover an example of how to select Binding and Interactions in the Examples section at the end of this chapter, but for now, let’s look at how you can hook these Actions to your code.
有多个使用输入系统的代码中的操作。在本节中,我将概述最重要的主题,这些主题应该可以帮助您开始使用输入系统。
There are multiple ways to work with the Input System’s Actions in your code. In this section, I will give a general overview of the most important topics that should allow you to get started working with the Input System.
要将您的操作连接到您的代码,您需要使用以下语句导入输入系统:
To connect your Actions to your code, you will need to import the InputSystem with the following statement:
使用 UnityEngine.InputSystem;
using UnityEngine.InputSystem;
我将讨论通过两种方式将操作与代码相连接:
There are two ways in which I will discuss connecting Actions to your Code:
笔记
Note
有关将操作连接到代码的其他方法的更多信息,请参阅以下Unity 文档:
For more information about alternate ways you can connect Actions to your code, see the following Unity documentation:
https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Workflows.xhtml
https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Workflows.xhtml
您可以引用 Action Asset 并创建InputActionsAsset 类型的变量,如下所示:
You can reference the Action Asset creating a variable of type InputActionsAsset like so:
[SerializeField] 私有 InputActionAsset 动作;
[SerializeField] private InputActionAsset actions;
然后您可以在检查器 (Inspector) 中分配操作的值。
You can then assign the value of actions in the Inspector.
为了引用InputActionAsset中的特定 Action ,您可以创建一个InputAction类型的变量,如下所示:
And to reference the specific Action within a InputActionAsset, you could create a variable of type InputAction, like so:
私人输入动作玩家动作;
private InputAction playerAction;
然后,您可以通过在动作资产中查找特定的动作映射和动作来分配它,如下所示:
You could then assign it by finding the specific Action Map and Action within the Action Asset, like so:
playerMoveAction = action.FindActionMap("玩家").FindAction("移动");playerMoveAction = actions.FindActionMap("Player").FindAction("Move"); 一旦你找到参考 Action Asset,您需要启用和禁用适当的动作地图。例如,如果您有一个名为Player 的动作地图,则可以执行以下操作:
Once you find the reference to the Action Asset, you will need to enable and disable the appropriate Action Maps. For Example, if you had an Action Map named Player, you could do the following:
私有无效OnEnable()
{
动作.FindActionMap("玩家").Enable();
}
私有无效OnDisable()
{
动作.FindActionMap(“玩家”).Disable();
}private void OnEnable()
{
actions.FindActionMap("Player").Enable();
}
private void OnDisable()
{
actions.FindActionMap("Player").Disable();
} 获得对InputActionAsset的引用后,您可以让您的方法订阅 Action 的各种回调。每个 Action 都有以下可订阅的回调:
Once you have a reference to the InputActionAsset, you can have your methods subscribe to the various callbacks of the Action. Each Action has the following callbacks that you can subscribe to:
举例来说,您可以订阅名为Player 的动作地图上名为Jump 的动作,该动作由按下按钮触发,如下所示:
So, for example, you could subscribe to an Action named Jump on an Action Map named Player that is triggered by a button press with something like the following:
action.FindActionMap("玩家").FindAction("跳跃").performed += OnJump;actions.FindActionMap("Player").FindAction("Jump").performed += OnJump; 如果你想投票行动,而不是订阅回调事件,可以使用ReadValue<TValue>()方法,如下所示:
If you wish to poll the Actions rather than subscribe to the callback events, you use the ReadValue<TValue>() method like so:
Vector2 moveVector = playerMoveAction.ReadValue<Vector2>();
Vector2 moveVector = playerMoveAction.ReadValue<Vector2>();
如果您不太喜欢编写代码,您可以使用PlayerInput组件。PlayerInput组件允许您指定各种操作调用 C# 脚本中的哪些方法。
If you’re not a big fan of writing code, you can instead use the PlayerInput component. The PlayerInput component will allow you to specify which methods in your C# scripts are called by the various Actions.
图 20.11:默认的玩家输入组件
Figure 20.11: The default Player Input component
使用此方法还是直接引用代码中的操作主要取决于个人喜好。它需要的代码较少,但需要更多的 Inspector 工作。作为一名职业程序员,我个人更喜欢在代码中引用操作的方法。我发现当有多个对象使用操作时,它更容易调试、更可自定义且编辑速度更快。但是,当我正在处理一个项目时,设计师在 Inspector 中进行更改,而他们不想在代码中工作,使用PlayerInput组件是一个很好的解决方案,因为它允许他们在没有我的情况下完成工作。您也可以使用组合,这完全取决于您的需求和偏好。
Using this method vs directly referencing the Actions in your code is mostly up to preference. It takes less code, but it takes more Inspector work. As a programmer by trade, I personally prefer the method that references the Actions in code. I find it easier to debug, more customizable, and quicker to edit when there are multiple objects using Actions. However, when I am working on a project that has designers making changes in the Inspector, who don’t want to work in code, using the PlayerInput component is a good solution as it allows them to do things without me. You can also use a combination and it’s all based on your needs and preferences.
笔记
Note
要了解有关使用代码访问操作的各种方法的更多信息,请参阅以下文档:
To learn more about the various ways you can access Actions with your code, see the following documentation:
https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Workflow-ActionsAsset.xhtml
https://docs.unity3d.com/Packages/com.unity.inputsystem@1.7/manual/Workflow-ActionsAsset.xhtml
现在我们有了关于 Unity 的总体思路输入系统如何工作,让我们看一些如何使用它的例子。
Now that we have a general idea of how the Unity Input System works, let’s look at some examples of how to work with it.
示例
Examples
现在我们已经回顾了输入系统入门基础知识,让我们看一些如何实现它的示例。我们将看一个非常基本的字符控制器示例。我们将从一个使用旧输入管理器的示例开始,然后调整它以使用输入系统。
Now that we’ve reviewed the basics of getting started with the Input System, let’s look at some examples of how to implement it. We’ll look at one very basic example of a character controller. We’ll start with an example that uses the old Input Manager and then adjust it to use the Input System.
笔记
Note
这是一个超基础的字符控制器。它被简化了,以便更容易理解将其转换为输入系统的过程。
This is a super basic character controller. It is simplified to make the process of converting it to the Input System easier to understand.
我们将使用的例子只是一只跳跃和移动的猫。
The example we will use is just a cat that jumps and moves around.
图 20.12:使用旧输入系统的字符控制器示例
Figure 20.12: The character controller example using the old Input System
在开始这些示例之前,请完成以下步骤:
Before you begin these examples, complete the following steps:
在你导入包后,播放场景第 20 章- 示例 1,了解猫是如何移动的。它没有什么特别花哨或令人印象深刻的,但猫会用空格键跳跃,用箭头键和A - D键来回移动。
After you’ve imported the package, play the scene Chapter 20 – Example 1 to get a feel of how the cat moves around. It’s nothing fancy or particularly impressive, but the cat will jump with the space bar and move back and forth with the arrow keys and A- D keys.
无效更新()
{
运动 = Input.GetAxis("水平");
catRigidbody.velocity = new Vector2(速度 * 运动, catRigidbody.velocity.y);
if (grounded && Input.GetButtonDown("Jump"))
{
catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
}
}注意Input.GetAxis("Horizontal") 和Input.GetButtonDown ("Jump")的使用。这些是在输入管理器中定义的,您可以在编辑|项目设置|输入管理器中找到它。
void Update()
{
movement = Input.GetAxis("Horizontal");
catRigidbody.velocity = new Vector2(speed * movement, catRigidbody.velocity.y);
if (grounded && Input.GetButtonDown("Jump"))
{
catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
}
}Notice the use of Input.GetAxis("Horizontal") and Input.GetButtonDown ("Jump") . These are defined within the Input Manager which you can find in Edit | Project Settings | Input Manager.
如果您现在尝试玩游戏,您将在控制台中看到以下错误。
InvalidOperationException:您正在尝试使用 UnityEngine.Input 类读取输入,但是您已在播放器设置中将活动输入处理切换为输入系统包。 InputManagerBasicCharacterController.Update()(位于 Assets/Scripts/InputManagerBasicCharacterController.cs:20)
这是因为当我们安装输入系统时,项目停止接受使用UnityEngine.Input的输入。猫将不再响应我们的按键而移动。
If you try to play the game now, you will see the following error in your console.
InvalidOperationException: You are trying to read Input using the UnityEngine.Input class, but you have switched active Input handling to Input System package in Player Settings. InputManagerBasicCharacterController.Update () (at Assets/Scripts/InputManagerBasicCharacterController.cs:20)
This is because when we installed the Input System, the project stopped accepting inputs that use UnityEngine.Input. The cat will no longer move in response to our key presses.
好的,现在我们启动并设置好我们的项目后,我们可以将该代码转换为输入系统可以使用的代码!让我们从设置操作开始。
Ok, now that we have our project started and set up, we can convert that code to something that can be used by the Input System! Let’s start by setting up our Actions.
开始之前调整我们的代码,我们首先必须设置我们的动作地图和动作。
Before we start adjusting our code, we first have to set up our Action Map and Actions.
要设置基本角色控制器操作,请完成以下步骤:
To set up your basic character controller Actions, complete the following steps:
图 20.13:选择自动保存
Figure 20.13: Selecting Auto-Save
图 20.14:玩家行动图
Figure 20.14: The Player Action Map
图 20.15:跳跃动作的属性
Figure 20.15: The Jump Action’s properties
图 20.16:跳跃动作的绑定
Figure 20.16: The Jump Action’s bindings
图 20.17:空格键 [键盘] 绑定上的按压交互
Figure 20.17: The Press Interaction on the Space [Keyboard] binding
图 20.18: 2D向量绑定
Figure 20.18: The 2D vector bindings
图 20.19:左侧和右侧绑定
现在,我们已将箭头键绑定到移动动作。
Figure 20.19: The Left and Right Bindings
We have now bound our arrow keys to the move action.
图 20.20:所有必要的操作和绑定
Figure 20.20: All the necessary Actions and Bindings
现在我们已经完成了操作的连接,我们可以开始在代码中使用它们了!我将向您展示两种方法要做到这一点:使用PlayerInput组件并引用我们脚本中的动作。
Now that we’re done hooking up our Actions, we can start using them with our code! I’ll show you two ways to do this: with the PlayerInput Component and by referencing the Action in our script.
切换我们的代码使用Actions和PlayerInput组件,需要一些代码调整和一些Inspector工作。
Switching our code to use Actions and the PlayerInput component, requires a bit of code adjustment and some Inspector work.
要将操作与PlayerInput组件结合使用,请完成以下步骤:
To use Actions with the PlayerInput Component, complete the following steps:
使用 UnityEngine.InputSystem;
using UnityEngine.InputSystem;
公共 void OnJump(InputAction.CallbackContext 上下文){
}public void OnJump(InputAction.CallbackContext context) {
}if (grounded && Input.GetButtonDown("Jump"))
{
catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
}将其剪切并粘贴到OnJump()方法中。
if (grounded && Input.GetButtonDown("Jump"))
{
catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
}Cut and paste it into the OnJump() method.
公共 void OnJump(InputAction.CallbackContext 上下文){
如果(接地){
catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
}
}public void OnJump(InputAction.CallbackContext context) {
if (grounded) {
catRigidbody.AddForce(new Vector2(catRigidbody.velocity.x, jumpHeight));
}
}图 20.21:Cat 上的一些组件
Figure 20.21: Some of the components on Cat
图 20.22:将 CatActions 分配给 PlayerInput 组件
Figure 20.22: Assigning CatActions to the PlayerInput component
Notice it already found our Player Action Map and added it as the Default Map. If we had more Action Maps, there would be others in the dropdown we could choose from.
图 20.23:各种玩家动作事件
Figure 20.23: The Various Player Action Events
图 20.24: 玩家跳跃事件调用我们的 OnJump 方法
玩游戏,当你按下空格键时你应该会看到猫跳跃。
Figure 20.24: The Player Jump Event calling our OnJump method
Play the game and you should see the cat jump when you press the spacebar.
公共 void OnMove(InputAction.CallbackContext context) {¶}public void OnMove(InputAction.CallbackContext context) {¶}私有 Vector2 moveVector = new Vector2();
private Vector2 moveVector = new Vector2();
运动 = Input.GetAxis("水平");在前面的代码中,移动是一个浮点变量。水平轴返回一个浮点数,而移动操作发送的是Vector2值。将以下代码添加到OnMove()方法中:
移动向量 = 上下文.读取值<Vector2>();
每当调用OnMove()方法时,这将获取移动动作的值。
movement = Input.GetAxis("Horizontal");In the preceeding code, movement was a float variable. While the Horizontal Axis returned a float, the Move Action sends Vector2 value. Add the following line to your OnMove() method:
moveVector = context.ReadValue<Vector2>();
This will get the value of the Move Action whenever the OnMove() method is called.
无效更新()
{
运动 = Input.GetAxis("水平");
catRigidbody.velocity = new Vector2(速度 * 运动, catRigidbody.velocity.y);
}void Update()
{
movement = Input.GetAxis("Horizontal");
catRigidbody.velocity = new Vector2(speed * movement, catRigidbody.velocity.y);
}运动 = 输入.GetAxis("水平");Movement = Input.GetAxis("Horizontal");catRigidbody.速度 = new Vector2(速度 * 移动矢量.x, catRigidbody.速度.y);
catRigidbody.velocity = new Vector2(speed * moveVector.x, catRigidbody.velocity.y);
图 20.25: 玩家移动事件调用我们的 OnMove 方法
Figure 20.25: The Player Move Event calling our OnMove method
玩游戏,你的猫现在应该能够移动和跳跃!
Play the game and your cat should now be able to move and jump!
就我个人而言,我并不喜欢使用PlayerInput组件。由于我是一名程序员,我更喜欢将大部分时间花在代码编辑器上,而不是 Unity 编辑器上。我发现调试检查器中挂接的代码很难调试。当我使用此功能时,我会尝试通过在代码中添加注释来说明哪些组件调用了哪些方法,以便将来的自己(或我的其他编程同事)的工作更轻松。这可以使调试和理解代码的工作方式变得更容易一些。使用像 Jetbrains Rider 这样的代码编辑器可以减轻一些压力,因为它会指示代码在检查器中挂接的位置,但它并不总是显示所有必要的信息,而且您不能指望您的同事也拥有Rider IDE。如果您有类似的偏好,我将在下一个示例中向您展示如何以以代码为中心的方式执行此操作。
Personally, I’m not a fan of using the PlayerInput component. Since I am a programmer by trade, I prefer to spend most of my time in my code editor rather than the Unity Editor. I find debugging code that is hooked up in the inspector difficult to debug. When I do use this functionality, I try to make life easier for my future self (or my other programming coworkers) by adding comments to the code that states what components call what methods. This can make debugging and understanding the way the code works a little easier. Using a code editor like Jetbrains Rider can relieve some of this stress, as it will indicate where code is hooked up in the Inspector, but it does not always show all necessary information and you can’t count on your coworkers also having the Rider IDE. If you have a similar preference, I will show you in the next example how to do this in a code-centric way.
To redo our previous example so that it references the Actions in code, complete the following steps:
[SerializeField] 私有 InputActionAsset 动作;
[SerializeField] private InputActionAsset actions;
我更喜欢将OnEnable()方法放在Awake()方法之下,因为它在该方法之后执行,并将OnDisable()方法放在脚本的底部,因为它将在我们所有其他方法之后执行。
I prefer to put the OnEnable() method right under the Awake() method since it executes after it and the OnDisable() method at the bottom of the script since it will execute after all of our other methods.
私有无效OnEnable()
{
动作.FindActionMap("玩家").Enable();
}
私有无效OnDisable()
{
动作.FindActionMap(“玩家”).Disable();
}private void OnEnable()
{
actions.FindActionMap("Player").Enable();
}
private void OnDisable()
{
actions.FindActionMap("Player").Disable();
}action.FindActionMap("玩家").FindAction("跳跃").performed += OnJump;这应该足以让我们已经编写的OnJump()方法与您的 Action Map 配合使用。玩游戏,现在您可以看到猫在您按下空格键时跳跃。
actions.FindActionMap("Player").FindAction("Jump").performed += OnJump;This should be enough to get the OnJump() method we already wrote working with your Action Map. Play the game and you can now see the cat jump when you press the space bar.
action.FindActionMap("玩家").FindAction("跳跃").performed -= OnJump;actions.FindActionMap("Player").FindAction("Jump").performed -= OnJump;私人输入动作 playerMoveAction;
private InputAction playerMoveAction;
playerMoveAction = action.FindActionMap("玩家").FindAction("移动");playerMoveAction = actions.FindActionMap("Player").FindAction("Move");移动向量 = 上下文.读取值<Vector2>();
moveVector = context.ReadValue<Vector2>();
无效更新()
{
移动向量 = playerMoveAction.ReadValue<Vector2>();
catRigidbody.速度 = new Vector2(速度 * 移动矢量.x, catRigidbody.速度.y);
}这足以让我们的猫咪通过 Actions 移动,而无需PlayerInput组件。玩游戏,当你按下相应的键时,观察猫咪移动和跳跃。
void Update()
{
moveVector = playerMoveAction.ReadValue<Vector2>();
catRigidbody.velocity = new Vector2(speed * moveVector.x, catRigidbody.velocity.y);
}And that should be sufficient to get our cat moving with Actions and without the PlayerInput component. Play the game and watch the cat move around and jump when you press the appropriate keys.
我知道将我们的特定项目设置为使用输入系统而不是使用输入管理器需要做更多的工作,而且似乎不值得。对于这个小例子来说,这可能是真的。但是,如果我们想创建一个跨平台版本的项目,接受来自多种设备类型的输入,那么在与为每个可能的输入配置添加一行新代码相比,Action Map 的工作量要小得多我们想与之合作。
I know that setting our specific project up to use the Input System rather than using the Input Manager was a lot more work and may not seem worth it. And for this tiny example, that is probably true. However, if we wanted to create a cross platform version of this project that accepted inputs from multiple types of devices, adding new Bindings to an Action within an Action Map is significantly less work than adding in a new line of code for each possible input configuration we want to work with.
在本章中,我们介绍了如何使用新输入系统为您的游戏收集输入。这使我们能够制作一个易于定制的输入系统,从长远来看,该系统可以使通过各种输入设备控制我们的游戏变得更加容易。设置可能需要一些工作,但从长远来看,它可以为您节省很多精力。
In this chapter, we covered how to use the New Input System to collect input for your game. This allows us to make an easily customizable input system that can make controlling our game via various input devices significantly easier in the long run. It may take a bit if set work to set up, but it can save you a lot of effort in the long run.
好了,这本书到此结束了!我没有更多的用户界面知识可以传授给你了。
And so, we come to an end of the book! I have no more User Interface knowledge to impart to you.
由于此电子书版本没有固定页码,下面的页码仅基于本书的印刷版超链接供参考。
As this ebook edition doesn't have fixed pagination, the page numbers below are hyperlinked for reference only, based on the printed edition of this book.
符号
Symbols
2D 游戏背景图像
2D game background image
2D World Space status indicators 478-481
3D hovering health bars 481-487
一个
A
认知和 情感
cognitive and emotional 48
听力和 言语
hearing and speech 47
流动 性
mobility 47
参考链接 48
reference link 48
动作编辑器 584
Action Editor 584
行动地图 584
Action Map 584
操作
Actions
美国信息交换标准代码 (ASCII) 247
American Standard Code for Information Interchange (ASCII) 247
Android 屏幕分辨率
Android screen resolutions
参考链接 19
reference link 19
动画文本
animated text
创建 259
creating 259
Animation Parameters. setting in scripts 407, 408
Animator of Transition Animations 405, 406
动画师的参数
Animator’s Parameters
ASCII 码表
ASCII Codes Table
参考链接 247
reference link 247
用于设计用户界面 (UI) 37
used, for designing user interface (UI) 37
自动布局组
automatic layout groups
网格布局组 116
Grid Layout Group 116
水平布局组 110
Horizontal Layout Group 110
垂直布局组 115
Vertical Layout Group 115
乙
B
背景画布预制件
Background Canvas prefab
基本输入模块 162
BaseInputModule 162
基准测试 491
benchmarking 491
广告牌效果 477
billboard effect 477
按钮动画过渡
button Animation Transition
按钮控制 568
Button Control 568
按下按钮
Button presses
used, for loading scenes 222-224
按钮
buttons
explicit navigation, selecting 217-221
首先选择,使用 210
First Selected, using 210
碳
C
C#
C#
UI,可与513交互
UI, making interactable with 513
画布组件 64
Canvas component 64
Canvas Renderer 组件 74
Canvas Renderer component 74
Canvas Scalar 组件 68
Canvas Scalar component 68
恒定物理尺寸 71
Constant Physical Size 71
世界 72
World 72
插入符号 375
caret 375
C# 代码
C# code
使用,利用网络数据制作 VisualElement 和 Label 属性 554 - 563
using, to make VisualElement and Label properties with web data 554-563
中央处理器 (CPU) 490
Central Processing Unit (CPU) 490
字符间距
character spacing
圆形进度条
circular progress bar
复杂的战利品箱
complex loot box
animations, setting up 429-436
State Machine, building 436, 437
控制 567
Controls 567
协程 532
coroutines 532
character spacing, adjusting 279-281
德
D
大字体
DaFont
URL 245
URL 245
为不便而设计的概念 44
designing for inconvenience concept 44
设备特定资源 29
device-specific resources 29
对话
dialogue
叙事界面 5
diegetic interface 5
绘制调用 491
draw call 491
标题属性 364
caption properties 364
值改变时 (Int32) 365
On Value Changed (Int32) 365
选项属性 364
option properties 364
模板属性 364
template properties 364
下拉式菜单
dropdown menu
创建,使用图像 380
creating, with images 380
information, using from dropdown selection 388-390
laying out, with caption and item images 380-383
埃
E
八向虚拟模拟摇杆
eight-directional virtual analog stick
事件系统管理器 153
Event System Manager 153
阻力阈值属性 154
Drag Threshold property 154
首选房产 153
First Selected property 153
发送导航事件属性 154
Send Navigation Events property 154
事件触发器组件 163
Event Trigger component 163
action, adding to event 166, 167
事件类型 164
event types 164
事件类型 164
event types 164
拖放事件 165
drag and drop events 165
其他 活动
other events 165
指针事件 164
pointer events 164
选择事件 165
selection events 165
扩展现实 (XR) 31
extended reality (XR) 31
F
F
视场 (FOV) 34
field of view (FOV) 34
钳工 126
Fitters 126
Aspect Ratio Fitter component 127, 128
Content Size Fitter component 126, 127
菲茨定律 7
Fitts’ Law 7
字体资源创建器
Font Asset Creator
参考链接 253
reference link 253
字体资源
font assets
参考链接 252
reference link 252
字体颜色
font color
修改,带标记 255
modifying, with markup 255
字体
fonts
上升计算模式 248
Ascent Calculation Mode 248
角色属性 247
Character property 247
dynamic font settings 248, 249
字体资源 253
font assets 253
字体大小设置 246
Font Size setting 246
字体样式,导入 250
font styles, importing 250
渲染模式设置 247
Rendering Mode setting 247
与245合作
working with 245
字体大小
font size
修改,带标记 255
modifying, with markup 255
字体松鼠
Font Squirrel
URL 245
URL 245
字体样式
font style
修改,带标记 254
modifying, with markup 254
帧速率 490
Frame Rate 490
每秒帧数 (fps) 490
frames per second (fps) 490
格
G
游戏
game
single resolution, building 14, 15
游戏视图
Game view
mobile resolution, setting 18, 19
GetAxis() 函数 159
GetAxis() function 159
GetButton() 函数 158
GetButton() function 158
GetKey() 函数 159
GetKey() function 159
GetMouseButton() 函数 160
GetMouseButton() function 160
字形指标
Glyph metrics
参考链接 248
reference link 248
Google 字体
Google Fonts
URL 245
URL 245
图形用户界面 (GUI)
graphical user interface (GUI)
Graphic Raycaster component 72, 73, 170
图形处理单元 (GPU) 490
Graphics Processing Unit (GPU) 490
电网库存
grid inventory
细胞大小 117
Cell Size 117
Start Corner property 118, 120
赫
H
heads-up-display (HUD) 4, 33, 64
水平健康栏
horizontal health bar
Horizontal Layout Group 110, 111
Child Alignment property 112, 113
Child Force Expand property 114, 115
控件子项 Size 属性 113
Control Child Size property 113
逆序排列属性 113
Reverse Arrangement property 113
间距属性 112
Spacing property 112
使用 Child Scale 属性 115
Use Child Scale property 115
HUD 选择菜单
HUD selection menu
我
I
图像类型属性,UI 图像
Image Type property, UI Image
简单 289
Simple 289
平铺 291
Tiled 291
立即模式图形用户界面 (IMGUI) 502
Immediate Mode Graphical User Interface (IMGUI) 502
示例 571
examples 571
在检查器 571
in Inspector 571
概述 566
overview 566
using, to create Inspector button 573-578
using, to show framerate in-game 572, 573
输入
input
for accelerometer and gyroscope 162, 163
用于多点触控 162
for multi-touch 162
输入字段组件 366
Input Field component 366
字符限制属性 367
Character Limit property 367
Character Validation options 373-375
Content Type property 367, 368
隐藏移动输入属性 367
Hide Mobile Input property 367
输入类型 369
Input Types 369
线型选项 369
Line Type option 369
On Value Changed (String) and On End Edit (String) 375, 376
占位符属性 367
Placeholder property 367
插入符号和选择属性 375
properties, of caret and selection 375
属性,输入的文本和屏幕键盘 367
properties, of entered text and onscreen keyboards 367
只读属性 367
Read Only property 367
文本属性 367
Text property 367
输入函数
input functions
按钮和按键 158
for buttons and key presses 158
获取轴 159
GetAxis 159
获取按钮 158
GetButton 158
获取密钥 159
GetKey 159
获取鼠标按钮 160
GetMouseButton 160
参考链接 157
reference link 157
using, with Pause Panel 174, 176
输入模块 160
input modules 160
基本输入模块 162
BaseInputModule 162
指针输入模块 162
PointerInputModule 162
独立输入模块 161
Standalone Input Module 161
Actions to code, connecting 586-589
polling, versus subscribing 581-583
输入系统,示例
Input System, examples
basic character controller Actions, creating 591-594
基本角色控制器,通过引用代码中的 Actions 创建 600 - 602
basic character controller, creating by referencing Actions in your code 600-602
基本角色控制器,使用 PlayerInput 组件创建 595 - 599
basic character controller, creating with PlayerInput Component 595-599
输入系统 53
Input System 53
and new Input System, selecting between 54, 55
输入管理器 54
Input Manager 54
新输入系统 54
new Input System 54
可交互的用户界面
interactable UI
接口 168
interface 168
库存物品
inventory items
库存面板
Inventory Panel
KeyCode,与172 - 174一起使用
invisible button zones 207, 208
钾
K
键码
KeyCode
using, with Inventory Panel 172-174
关键帧 226
keyframes 226
大号
L
弹性宽度和弹性高度属性 125
Flexible Width and Flexible Height properties 125
Ignore Layout property 122, 123
最小宽度和最小高度属性 123
Min Width and Min Height properties 123
首选宽度和首选高度属性 124
Preferred Width and Preferred Height properties 124
宽度和高度属性 123
Width and Height properties 123
米
M
MagicLeap
MagicLeap
参考链接 37
reference link 37
标记格式
markup format
探索 254
exploring 254
用于修改字体颜色和大小 255
used, for modifying font color and size 255
用于修改字体样式 254
used, for modifying font style 254
面具
masks
使用 328
using 328
元接口 5
meta interface 5
Milky Coffee 字体
Milky Coffee font
参考链接 261
reference link 261
参考链接 37
reference link 37
用于设计用户界面 (UI) 37
used, for designing user interface (UI) 37
移动宽高比
mobile aspect ratio
设置 18
setting 18
移动输入 29
mobile inputs 29
移动方向
mobile orientation
设置 18
setting 18
移动分辨率
mobile resolution
设置 18
setting 18
多点触控输入 162
multi-touch input 162
静音按钮
mute Buttons
否
N
命名空间 150
namespace 150
新输入系统 54
new Input System 54
节点 400
nodes 400
非叙事类别 5
non-diegetic category 5
Noto 字体
Noto fonts
参考链接 247
reference link 247
哦
O
对象池 496
object pooling 496
优化基础 490
optimization basics 490
CPU 491
CPU 491
帧速率 490
Frame Rate 490
图形处理器 490
GPU 490
磷
P
平移和缩放
pan and zoom
implementing, with mouse and multi-touch input 188-194
面板设置 507
Panel Settings 507
粒子系统
Particle System
时间,播放战利品盒动画 467
timing, to play in loot box animation 467
粒子系统,显示在 UI 中
Particle System, that displays in UI
暂停面板
Pause Panel
Input Manager, using with 174-176
物理 2D 射线投射器 170
Physics 2D Raycaster 170
指针输入模块 162
PointerInputModule 162
投票
polling 582
弹出动画
pop in and out animation
弹出菜单
pop-up menus
隐藏,通过按键 171
hiding, with keypress 171
显示,按键 171
showing, with keypress 171
弹出窗口
pop-up windows
动画,淡入淡出 411
animating, to fade in and out 411
预制 259
prefab 259
按住/长按功能
press-and-hold/long-press functionality
发布者-订阅者 (pub-sub) 模式 582
publisher-subscriber (pub-sub) pattern 582
R
R
射线投射器 170
raycasters 170
图形光线投射器 170
Graphic Raycaster 170
物理 2D 射线投射器 170
Physics 2D Raycaster 170
光线投射 170
raycasting 170
矩形遮罩 2D
Rect Mask 2D
矩形工具 76
Rect Tool 76
Rect Transform component 63, 64, 76-78
矩形工具 76
Rect Tool 76
重复按钮 569
RepeatButton 569
年代
S
场景
scene
Scroll Rect component 337, 338
On Value Changed event 341, 342
序列搜索者 27
Sequence Seekers 27
射击场风格的游戏
shooting gallery style game
参考链接 273
reference link 273
On Value Changed (Single) 357, 358
空间界面 5
spatial interface 5
独立输入模块 161
Standalone Input Module 161
状态机 400
state machine 400
静态四向虚拟方向键
static four-directional virtual D-Pad
样式表 507
Style Sheet 507
订阅
subscribing
电视
T
文本
text
文本区域控件 569
TextArea Controls 569
文本框 文本
text box text
文本框窗口
text box windows
TextField 控件 569
TextField Control 569
文本网格Pro 281 , 358 , 359 , 377 , 378
TextMeshPro 281, 358, 359, 377, 378
控制设置 379
Control settings 379
输入字段设置 379
Input Field settings 379
选择时(字符串)和取消选择时(字符串) 379
On Select (String) and On Deselect (String) 379
参考链接 237
reference link 237
used, for creating wrapped text 282-286
Text Input properties 238, 239
TextMeshPro Project Settings 243, 245
纹理打包器
TexturePacker
参考链接 273
reference link 273
主题样式表 508
Theme Style Sheet 508
On Value Changed (Boolean) events 352, 353
切换控制 570
Toggle Control 570
切换组组件 354
Toggle Group component 354
工具,用于确定绩效 491
tools, for determining performance 491
Unity 帧调试器 494
Unity Frame Debugger 494
过渡属性,UI 按钮 200
Transition property, UI Button 200
动画过渡 204
Animation transition 204
Color Tint transition 200, 202
无 200
None 200
Sprite Swap transition type 202-204
乌
U
UI 构建器
UI Builder
using, to lay out menu 538-554
using, to make animation transitions 538-554
using, to make Editor Window’s UI 518-527
using, to make style sheets 538-554
按钮组件 200
Button component 200
过渡属性 200
Transition property 200
画布组件 64
Canvas component 64
Canvas Renderer 组件 74
Canvas Renderer component 74
Canvas Scalar 组件 68
Canvas Scalar component 68
Graphic Raycaster component 72, 73
Rect Transform component 63, 64
UI 文档 507
UI Document 507
使用 513
using 513
UI 下拉菜单 358
UI Dropdown 358
UI 效果组件 292
UI effect components 292
位置为 UV1 组件 294
Position As UV1 component 294
UI 元素
UI elements
访问,代码 150
accessing, in code 150
布局 6
laying out 6
UnityEngine.UI 命名空间 150
UnityEngine.UI namespace 150
UIElements namespaces 514, 515
UI Image component properties 288, 289
图像类型属性 289
Image Type property 289
UI 输入字段 365
UI Input Field 365
创造 365
creating 365
输入字段组件 366
Input Field component 366
UI 滚动条
UI Scrollbars
执行
implementing 330
UI 滚动条,组件
UI Scrollbars, component
On Value Changed event 332-334
UI 滚动视图
UI Scroll View
示例 342
examples 342
making, from pre-existing menu 342-348
Scroll Rect component 337, 338
UI 滑块 355
UI Slider 355
创造 355
creating 355
图像用户界面 52
IMGUI 52
选择 53
selecting between 53
UI 工具包 53
UI Toolkit 53
Unity 用户界面 (uGUI) 52
Unity UI (uGUI) 52
角色属性 233
Character property 233
颜色属性 235
Color property 235
材料特性 235
Material property 235
Raycast Padding 属性 236
Raycast Padding properties 236
Raycast Target 属性 236
Raycast Target property 236
参考链接 243
reference link 243
文本属性 233
Text property 233
UI 切换 350
UI Toggle 350
切换组件 350
Toggle component 350
切换组组件 354
Toggle Group component 354
使用 350
using 350
UI 工具包 53
UI Toolkit 53
示例 517
examples 517
using, to make Editor virtual pet 517, 518
使用,制作带有样式表和动画过渡的菜单 537
using, to make menu with style sheets and animation transitions 537
UI 工具包
UI Toolkit package
UI工具包系统
UI Toolkit system
面板设置 507
Panel Settings 507
样式表 507
Style Sheet 507
UI 文档 507
UI Document 507
Unity 编辑器
Unity Editor
Unity 可扩展标记语言 (UXML) 507
Unity Extensible Markup Language (UXML) 507
Unity 帧调试器 494
Unity Frame Debugger 494
Unity UI优化策略 495
Unity UI optimization strategies 495
Layout Groups use, minimizing 495, 496
多个画布和画布层次结构,使用 495
multiple Canvases and Canvas Hierarchies, using 495
物品丢失 496
objects, missing 496
射线投射计算,减少 497
Raycast computations, reducing 497
time object pooling, enabling/disabling 496, 497
通用设计 39
universal design 39
通用设计原则 40
universal design principles 40
公平使用
equitable use 40
灵活性使用
flexibility use 41
低体力劳动
low physical effort 44
可感知信息
perceptible information 43
简单直观的使用 42
simple and intuitive use 42
规模和空间 方法
size and space approach 45
尺寸和空间 利用
size and space use 45
容错率
tolerance of error 43
creating, with UI Builder 511, 512
为 AR 37设计
designing, for AR 37
设计,适用于 MR 36
designing, for MR 36
使用 C# 513实现交互
making interactable, with C# 513
reference, getting to UI Documents variables 514, 515
UIElements 命名空间 514
UIElements namespaces 514
Visual Element 事件,管理 516
Visual Element events, managing 516
Visual Element properties, accessing 516, 517
五
V
Vertical Layout Group 115, 116
虚拟连续体
virtuality continuum
参考链接 33
reference link 33
used, for designing user interface (UI) 33, 34
可视化用户界面
visual UI
西
W
世界空间
World Space
工作考虑 477
working, considerations 477
世界空间用户界面
World Space UI
订阅我们的在线数字图书馆,即可全面访问 7,000 多本书籍和视频,以及行业领先的工具,帮助您规划个人发展并推进职业发展。如需更多信息,请访问我们的网站。
Subscribe to our online digital library for full access to over 7,000 books and videos, as well as industry leading tools to help you plan your personal development and advance your career. For more information, please visit our website.
您是否知道 Packt 为每本已出版的书籍提供电子书版本,并提供 PDF 和 ePub 文件?您可以在packtpub.com升级到电子书版本,作为印刷书籍客户,您有权享受电子书折扣。请通过customercare@packtpub.com与我们联系以了解更多详情。
Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at packtpub.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at customercare@packtpub.com for more details.
在www.packtpub.com,您还可以阅读一系列免费技术文章,注册一系列免费新闻通讯,并获得 Packt 书籍和电子书的独家折扣和优惠。
At www.packtpub.com, you can also read a collection of free technical articles, sign up for a range of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.
如果你喜欢这本书,你可能会对Packt 的其他书籍感兴趣:
If you enjoyed this book, you may be interested in these other books by Packt:
Unity 2022 移动游戏开发
Unity 2022 Mobile Game Development
约翰· P·多兰
John P. Doran
国际标准书号:978-1-80461-372-6
ISBN: 978-1-80461-372-6
将 Unity 与 Blender 融为一体进行 3D游戏开发
Mind-Melding Unity and Blender for 3D Game Development
斯宾塞·格雷
Spencer Grey
国际标准书号:978-1-80107-155-0
ISBN: 978-1-80107-155-0
如果您有兴趣成为 Packt 的作者,请访问authors.packtpub.com并立即申请。我们与成千上万像您一样的开发人员和技术专业人士合作,帮助他们与全球技术社区分享他们的见解。您可以进行一般申请,申请我们正在招募作者的特定热门主题,或提交您自己的想法。
If you’re interested in becoming an author for Packt, please visit authors.packtpub.com and apply today. We have worked with thousands of developers and tech professionals, just like you, to help them share their insight with the global tech community. You can make a general application, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.
你好!
Hi!
我是《使用 Unity 掌握 UI 开发》一书的作者 Ashley Godbold 。我真心希望您喜欢阅读这本书,并发现它有助于提高您的生产力和效率。
I am Ashley Godbold, author of Mastering UI Development with Unity. I really hope you enjoyed reading this book and found it useful for increasing your productivity and efficiency.
如果您能在亚马逊上留下评论分享您对这本书的看法,这将对我(和其他潜在读者!)非常有帮助。
It would really help me (and other potential readers!) if you could leave a review on Amazon sharing your thoughts on this book.
请访问以下链接留下您的评论:
Go to the link below to leave your review:
https://packt.link/r/180323539X
https://packt.link/r/180323539X
您的评论将帮助我们了解本书中哪些地方做得好,以及未来版本中哪些地方可以改进,所以我们真的很感激。
Your review will help us to understand what’s worked well in this book, and what could be improved upon for future editions, so it really is appreciated.
最好的祝愿,
Best wishes,
Ashley Godbold博士
Dr. Ashley Godbold
感谢您购买本书!
Thanks for purchasing this book!
您是否喜欢在旅途中阅读但又无法随身携带纸质书籍?
Do you like to read on the go but are unable to carry your print books everywhere?
您购买的电子书是否与您选择的设备不兼容?
Is your e-book purchase not compatible with the device of your choice?
别担心!现在购买每本 Packt 书籍,您都可以免费获得该书的无 DRM 的 PDF 版本。
Don’t worry!, Now with every Packt book, you get a DRM-free PDF version of that book at no cost.
随时随地、使用任何设备进行阅读。搜索、复制您喜爱的技术书籍中的代码,并将其直接粘贴到您的应用程序中。
Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.
福利不止于此,您还可以每天在收件箱中独家获得折扣、新闻通讯和精彩的免费内容
The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily
按照以下简单步骤即可获得好处:
Follow these simple steps to get the benefits:
https://packt.link/free-ebook/9781803235394
https://packt.link/free-ebook/9781803235394